ASP.Net MVC如何确定用户是否可以访问URL?

时间:2010-02-04 02:35:57

标签: asp.net-mvc authorization

因此,当您有用户登录时,我正在阅读有关登录循环的另一个问题,设置为返回登录后可能无法访问的URL(即管理员页面,并且用户使用正常账户)。

WebForms下的解决方案似乎是使用UrlAuthorizationModule.CheckUrlAccessForPrincipal方法。但是,对于使用授权属性保护的Action方法的URL不起作用。我想我可以找出URL指向哪个方法并反映它以解决我的问题 - 但我似乎无法弄清楚如何从路由表中获取此信息。

有人曾经使用过这个,或者有解决方案吗?如果我能从URL获取路由信息,我想我可以完成其余的工作,但是如果有人有一个通用的解决方案 - 即。一些隐藏的方法类似于前面提到的MVC,然后这也是非常棒的。

我不是问如何检查用户是否有权访问指定的控制器/操作对。我首先需要弄清楚如何根据URL从RouteTable获取Controller / Action对。所有背景故事的原因是,确实存在与MVC UrlAuthorizationModule.CheckUrlAccessForPrincipal相当的。

7 个答案:

答案 0 :(得分:8)

上面针对MVC 4更新了jfar的答案:

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;
    }
}

答案 1 :(得分:5)

我从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;
    }

它可以使用一些缓存,但对我来说这是一个过早的优化。

答案 2 :(得分:1)

这可能听起来有争议,但我在方法内部检查每个控制器方法开头的安全性:

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]属性的原因是您无法在运行时将id或任何其他标识信息传递给属性。

答案 3 :(得分: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");
    }

}

然后让我们说一个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
    ...
}

答案 4 :(得分:1)

在我的应用程序中,我创建了一个从AuthorizeAttribute派生的自定义过滤器,因此任何未经授权的访问都只会转到AccessDenied页面。对于链接,我将Html.ActionLink替换为自定义帮助程序Html.SecureLink。在此帮助程序扩展中,我检查此用户的角色对数据库的控制器/操作的访问权限。如果他/她有授权,则返回链接否则返回带有特殊备注的链接文本(可能是image / coloring / js)

答案 5 :(得分:0)

为什么不将控制器方法归于安全要求。

我写了一个属性,如下所示:

  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 >>



                }  
            }      
        }

答案 6 :(得分:0)

我只是花了一些时间来实现@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服务负责缓存,所以我不必担心性能。