ASP.Net MVC, как определить, может ли пользователь получить доступ к URL?
Итак, я читал еще один вопрос, касающийся цикла входа в систему, когда у вас есть вход в систему пользователя, чтобы вернуться к URL-адресу, к которому у них может не быть доступа после входа в систему (например, страница администратора, и пользователь входит в систему с помощью обычная учетная запись).
Решение под WebForms, похоже, должно использовать метод UrlAuthorizationModule.CheckUrlAccessForPrincipal
. Однако это не работает для URL-адресов, относящихся к методам действий, защищенным с помощью атрибута Authorize. Я решил, что могу решить, какой метод URL-адрес указывал и размышлял над ним, чтобы решить мою проблему, - но я не могу понять, как я получаю эту информацию из таблицы маршрутизации.
Кто-нибудь когда-либо работал с этим или имел решение для этого? Если я могу просто получить информацию о маршруте с URL-адреса, я думаю, что смогу работать с остальными, но если у кого-то есть общее решение - то есть. какой-то скрытый метод, похожий на ранее упомянутый для MVC, тогда это было бы также потрясающе.
Я не спрашиваю, как проверить, имеет ли пользователь доступ к указанной паре Controller/Action. Прежде всего мне нужно выяснить, как получить пару Controller/Action из RouteTable на основе URL. Причина для всей предыстории заключается в том, что действительно существует эквивалент UrlAuthorizationModule.CheckUrlAccessForPrincipal
для MVC.
Ответы
Ответ 1
В чем проблема, которую вы пытаетесь решить? Похоже, вы можете идти по пути к сложному решению, которое могло бы использовать простое решение.
Если у пользователя нет разрешений на доступ к странице после входа в систему, вы хотите, чтобы пользователи не регистрировались на одной странице, а зарегистрированные пользователи переходили на другую страницу?
Если в этом случае у меня может возникнуть соблазн создать другой контроллер только для таких сценариев и перенаправить на этот контроллер в любом месте, где у пользователя нет доступа. Или, если вы используете свой собственный базовый контроллер, я бы добавил туда функциональность.
Затем контроллер может представить желаемый вид. Например, если незарегистрированный пользователь пытается получить доступ к странице, они могут перенаправляться на общую страницу ошибок. Если пользователь вошел в систему, они могут перенаправляться на неавторизованную страницу.
Это очень похоже на ответ Роберта.
Вот базовый скелет для базового контроллера.
public BaseController: Controller
{
... // Some code
public ActionResult DisplayErrorPage()
{
// Assumes you have a User object with a IsLoggedIn property
if (User.IsLoggedIn())
return View("NotAuthorized");
// Redirect user to login page
return RedirectToAction("Logon", "Account");
}
}
Затем в let скажем действие AdminController (наследующее от BaseController) действие
public ActionResult HighlyRestrictedAction()
{
// Assumes there is a User object with a HasAccess property
if (User.HasAccess("HighlyRestrictedAction") == false)
return DisplayErrorPage();
// At this point the user is logged in and has permissions
...
}
Ответ 2
Ответ Джона Фаррелла (jfar) (класс SecurityTrimmingExtensions) обновлен для MVC 4:
public static class SecurityCheck
{
public static bool ActionIsAuthorized(string actionName, string controllerName)
{
IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory();
ControllerBase controller = factory.CreateController(HttpContext.Current.Request.RequestContext, controllerName) as ControllerBase;
var controllerContext = new ControllerContext(HttpContext.Current.Request.RequestContext, controller);
var controllerDescriptor = new ReflectedControllerDescriptor(controller.GetType());
var actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName);
AuthorizationContext authContext = new AuthorizationContext(controllerContext, actionDescriptor);
foreach (var authAttribute in actionDescriptor.GetFilterAttributes(true).Where(a => a is AuthorizeAttribute).Select(a => a as AuthorizeAttribute))
{
authAttribute.OnAuthorization(authContext);
if (authContext.Result != null)
return false;
}
return true;
}
}
Ответ 3
Я портировал и взломал этот код из MvcSitemap:
public static class SecurityTrimmingExtensions
{
/// <summary>
/// Returns true if a specific controller action exists and
/// the user has the ability to access it.
/// </summary>
/// <param name="htmlHelper"></param>
/// <param name="actionName"></param>
/// <param name="controllerName"></param>
/// <returns></returns>
public static bool HasActionPermission( this HtmlHelper htmlHelper, string actionName, string controllerName )
{
//if the controller name is empty the ASP.NET convention is:
//"we are linking to a different controller
ControllerBase controllerToLinkTo = string.IsNullOrEmpty(controllerName)
? htmlHelper.ViewContext.Controller
: GetControllerByName(htmlHelper, controllerName);
var controllerContext = new ControllerContext(htmlHelper.ViewContext.RequestContext, controllerToLinkTo);
var controllerDescriptor = new ReflectedControllerDescriptor(controllerToLinkTo.GetType());
var actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName);
return ActionIsAuthorized(controllerContext, actionDescriptor);
}
private static bool ActionIsAuthorized(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
if (actionDescriptor == null)
return false; // action does not exist so say yes - should we authorise this?!
AuthorizationContext authContext = new AuthorizationContext(controllerContext);
// run each auth filter until on fails
// performance could be improved by some caching
foreach (IAuthorizationFilter authFilter in actionDescriptor.GetFilters().AuthorizationFilters)
{
authFilter.OnAuthorization(authContext);
if (authContext.Result != null)
return false;
}
return true;
}
private static ControllerBase GetControllerByName(HtmlHelper helper, string controllerName)
{
// Instantiate the controller and call Execute
IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory();
IController controller = factory.CreateController(helper.ViewContext.RequestContext, controllerName);
if (controller == null)
{
throw new InvalidOperationException(
String.Format(
CultureInfo.CurrentUICulture,
"Controller factory {0} controller {1} returned null",
factory.GetType(),
controllerName));
}
return (ControllerBase)controller;
}
Он может использовать некоторое кэширование, но для моего случая это была преждевременная оптимизация.
Ответ 4
Это, вероятно, будет казаться спорным, но я проверить безопасность в начале каждого метода контроллера, внутри метода:
public class ProductController : Controller
{
IProductRepository _repository
public ActionResult Details(int id)
{
if(!_repository.UserHasAccess(id))
return View("NotAuthorized");
var item = _repository.GetProduct(id);
if (item == null)
return View("NotFound");
return View(item);
}
}
Причина, по которой я не использую атрибуты [Authorize]
, заключается в том, что вы не можете передать идентификатор или любую другую идентифицирующую информацию атрибуту во время выполнения.
Ответ 5
В моем приложении я создал собственный фильтр, полученный из AuthorizeAttribute, поэтому любой неавторизованный доступ просто перейдет на страницу AccessDenied. Для ссылок я заменяю Html.ActionLink специальным помощником Html.SecureLink. В этом вспомогательном расширении я проверяю доступ этих ролей пользователей к контроллеру/действию против базы данных. Если у него есть авторизация, обратная ссылка в противном случае возвращает текст ссылки со специальными примечаниями (может быть изображение/раскраска/js)
Ответ 6
Почему бы не атрибутировать ваши методы контроллера с требованием безопасности.
Я написал атрибут, чтобы сделать это следующим образом:
public class RequiresRoleAttribute : ActionFilterAttribute
{
public string Role { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (string.IsNullOrEmpty(Role))
{
throw new InvalidOperationException("No role specified.");
}
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
filterContext.HttpContext.Response.Redirect(loginUrl, true);
}
else
{
bool isAuthorised = filterContext.HttpContext.User.IsInRole(this.Role);
<< Complete Logic Here >>
}
}
}
Ответ 7
Я просто потратил некоторое время на реализацию решения @jfar (обновляя его для устаревшей версии GetFilters()), а затем я понял, что могу пропустить все это.
в моем случае (и я принимаю большинство случаев) У меня есть пользовательский атрибут AuthorizationAttribute для реализации авторизации сайта, который, в свою очередь, вызывает мою службу авторизации, чтобы сделать фактическое определение уровня доступа.
Итак, в моем html-помощнике для генерации ссылок меню я пропустил право на службу auth:
@Html.MenuItem(@Url, "icon-whatever", "TargetController", "TargetAction")
public static MvcHtmlString MenuItem(this HtmlHelper htmlHelper, UrlHelper url,string iconCss, string targetController, string targetAction)
{
var auth = IoC.Resolve<IClientAuthorizationService>().Authorize(targetController, targetAction);
if (auth == AccessLevel.None)
return MvcHtmlString.Create("");
* пользователь определяется внутри службы авторизации клиента
public string GetUser() {
return HttpContext.Current.User.Identity.Name;
}
* также может добавить некоторое поведение для доступа только для чтения. это хорошо, потому что моя служба auth заботится о кешировании, поэтому мне не нужно беспокоиться о производительности.