有关使用重写的AuthorizeAttribute,线程安全,ChildActions和缓存的MVC3的自定义安全设置的问题

时间:2011-05-20 15:50:17

标签: asp.net-mvc asp.net-mvc-3 caching thread-safety security

因此,在为我的MVC3应用程序搜索强大的安全解决方案后,我遇到了Rick Anderson的this blog post。它详细介绍了一个WhiteList方法,其中AuthorizeAttribute的自定义实现被应用为全局过滤器,并且您要使用虚拟属性AllowAnonymousAttribute来装饰您希望允许匿名访问的操作/控制器(我说虚拟,因为AllowAnonymousAttribute中没有逻辑,它是只是一个空的属性类)

bool allowAnnonymous = filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true)
|| filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true);
if (allowAnnonymous) return;

这(以及他的博客中提到的其他安全建议,如HTTPS)为我提供了一个默认的安全模型,我不需要对每个操作都应用安全检查,并且还要记住将其添加到将来的功能中加法。

问题的第一部分

现在,我没有在AuthorizeAttribute上使用Users / Roles属性,我需要从数据库中获取这些内容。对我而言,这是AuthorizeCore中的内容,因为它唯一的责任是返回一个真假,用户是否有权访问。但是我有一个问题,根据我对AuthorizeAttribute类的源代码的读取,AuthorizeCore必须是线程安全的,我不确定访问我的数据库以确定用户权限并坚持这一点的最佳方法。我的应用程序正在使用IoC,目前让我的IoC容器注入我的存储库处理AuthorizeAttribute的构造函数,但是通过这样做然后在AuthorizeCore中访问它,我不会导致线程安全问题吗?或者IoC实现和我用来向我的自定义AuthorizeAttribute构造函数提供参数的MVC3 DependencyResolver是否足以处理线程安全?注意,我的存储库使用的是UnitOfWork模式,其中包含我的nHibernate SessionFactory作为存储库的构造函数,而工作单元类是从我的IoC容器中提供的,由StructureMap使用下面的行实现,我在考虑这里使用的范围时是正确的会处理线程问题吗?

For<IUnitOfWork>().HybridHttpOrThreadLocalScoped().Use<UnitOfWork>();

问题的第二部分

我的数据模型(以及安全模型)的设置使得我的主要业务对象都被定义为一种大型层次结构模型,当我检查权限时,我会查看该层次结构模型中的位置默认情况下,已定义用户帐户并授予对其下所有内容的访问权限。 辅助权限检查是使用管理定义的业务逻辑权限的权限,例如角色X中的用户可以访问“删除窗口小部件”功能。为此,我使用Route数据并提取Controller和Action名称,并将它们与当前用户的详细信息结合使用Principal详细信息以访问我的数据库以解析此请求的权限。但是,对于页面上使用的每个ChildAction,也会重复这个逻辑,但是因为我使用了Route数据中的Controller和Action名称,所以我实际上并没有获得Child Action信息。它保留为父操作名称,而不是子操作,因为子操作未通过URL请求执行。这会导致对我的数据库进行冗余安全检查,以获取父操作和不必要的资源命中的详细信息。在研究这个问题时,我决定简单地绕过Child动作的安全性检查,并依赖于父动作。

bool bypassChildAction = filterContext.ActionDescriptor.IsDefined(typeof (ChildActionOnlyAttribute), true) || filterContext.IsChildAction;
if (bypassChildAction) return;

这样做是否有意义,如果是这样,为什么?在我看来,如果Action使用ChildActionOnlyAttribute进行修饰,那么无论如何都无法通过URL公开访问它。如果它作为子操作执行但不完全是子操作,我可以绕过安全检查只是为了执行此操作,因为父操作将处理权限。您是否会遇到需要限制访问子操作的情况?知道子操作通常是非常小的部分视图我不认为这是一个问题,但我也看到OnAuthorization的默认实现中的一行引用了一些关于缓存的问题。有谁知道这是否会影响我提出的解决方案?

摘要问题:

  • 多线程问题 访问用户权限 AuthorizeCore中的数据库
  • 安全问题绕过绕过 儿童行为的授权检查
  • 缓存对Child Actions的关注 与前一点相结合

非常感谢任何有关这些方面的意见或帮助!

2 个答案:

答案 0 :(得分:2)

Heya Yarx - 第1部分 - 在登录时缓存用户的所有权限。 因此,多线程访问不是问题,因为您的AuthorizeCore只是从缓存中获取角色,此时可以将其视为只读。

第2部分: 再次转到上面的第1点:) - 如果您的安全检查太重,为什么不在登录时加载用户的所有权限并缓存它们。点击您的子操作后,您可以请求权限,并在那时检查它们的缓存。

肯定有一种更好的方法可以解决这个问题。如果您在单个请求中多次访问数据库仅用于权限,则需要通过某种机制(自定义或实现其他基于声明的系统等)缓存权限集

我不是100%遵循您的机制,但基于路线的授权。你提到你正在从路线中提取信息 - 你能举个例子吗?

保护孩子的行为绝对有意义。如果两个视图调用Html.Action - 一个专门用作管理员而另一个被错误地复制并粘贴到另一个视图中怎么办?您的子操作应该始终受到保护,不要认为它们没问题,因为它们只是从另一个视图调用。

此外,如果您无法为用户缓存整个树,则可以在第一次调用AuthorizeCore时缓存安全检查。后续调用只会检查ex。缓存的角色 - 如果有,则使用它们,否则查看数据库。

答案 1 :(得分:0)

到目前为止我还有。我觉得我已经把它搞砸了,但我不确定如何用我想要满足的要求做到这一点。我是冒这个错了还是只是在脑子里?

public class LogonAuthorize : AuthorizeAttribute
{
    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        if (filterContext == null)
        {
            throw new ArgumentNullException("filterContext");
        }

        if (OutputCacheAttribute.IsChildActionCacheActive(filterContext))
        {
            // If a child action cache block is active, we need to fail immediately, even if authorization
            // would have succeeded. The reason is that there's no way to hook a callback to rerun
            // authorization before the fragment is served from the cache, so we can't guarantee that this
            // filter will be re-run on subsequent requests.
            throw new InvalidOperationException("AuthorizeAttribute cannot be used within a child action caching block."); //Text pulled from System.Web.Mvc.Resources
        }
        // Bypass authorization on any action decorated with AllowAnonymousAttribute, indicationg the page allows anonymous access and
        // does not restrict access anyone (Similar to a WhiteList security model).
        bool allowAnnonymous = filterContext.ActionDescriptor.IsDefined(typeof (AllowAnonymousAttribute), true) || filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof (AllowAnonymousAttribute), true);
        if (allowAnnonymous) return;

        if (CustomAuthorizeCore(filterContext))
        {
            HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
            cachePolicy.SetProxyMaxAge(new TimeSpan(0));
            cachePolicy.AddValidationCallback(CacheValidateHandler, filterContext); //CacheValidateHandler doesn't have access to our AuthorizationContext, so we pass it in using the data object.
        }

        HandleUnauthorizedRequest(filterContext);
    }

    private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
    {
        var filterContext = (AuthorizationContext)data;
        validationStatus = CustomOnCacheAuthorization(filterContext);
    }

    protected HttpValidationStatus CustomOnCacheAuthorization(AuthorizationContext filterContext)
    {
        if (filterContext.HttpContext == null)
        {
            throw new ArgumentNullException("filterContext.HttpContext");
        }

        bool isAuthorized = CustomAuthorizeCore(filterContext);
        return (isAuthorized) ? HttpValidationStatus.Valid : HttpValidationStatus.IgnoreThisRequest;
    }

    protected bool CustomAuthorizeCore(AuthorizationContext filterContext)
    {
        HttpContextBase httpContext = filterContext.HttpContext;

        if (httpContext == null)
            throw new ArgumentNullException("filterContext.HttpContext");

        Trace.WriteLine("Current User: " + (httpContext.User.Identity.IsAuthenticated ? httpContext.User.Identity.Name : "Anonymous"));
        if (!httpContext.User.Identity.IsAuthenticated)
            return false;

        string objectId = (httpContext.Request.RequestContext.RouteData.Values["id"] ?? Guid.Empty).ToString();
        Trace.WriteLine("Hierarchy Permissions check for Object: " + objectId);

        string controllerName = httpContext.Request.RequestContext.RouteData.GetRequiredString("controller");
        string actionName = httpContext.Request.RequestContext.RouteData.GetRequiredString("action");
        Trace.WriteLine("Policy Permissions check for Controller: " + controllerName + ", and Action: " + actionName);
        //if(!CheckHierarchyPermissions  || (!CheckHierarchyPermissions && !CheckBusinessLogicPermissions))
        //{
        //    //Check database permissions by getting DB reference from DependancyResolver
        //    DependencyResolver.Current.GetService(typeof (SecurityService)); //change this to an interface later
        //    return false;
        //}

        return true;
    }

    #region Old methods decorated with Obsolete() attributes to track down unintended uses
    [Obsolete("This overridden implementation of AuthorizeAttribute does not use the Users collection.", true)]
    public new string Users { get; set; }

    [Obsolete("This overridden implementation of AuthorizeAttribute does not use the Roles collection.", true)]
    public new string Roles { get; set; }

    [Obsolete("This overridden implementation of AuthorizeAttribute does not use the AuthorizeCore method.", true)]
    protected new bool AuthorizeCore(HttpContextBase httpContext)
    {
        return false;
    }

    [Obsolete("This overridden implementation of AuthorizeAttribute does not use the OnCacheAuthorization method.", true)]
    protected new virtual HttpValidationStatus OnCacheAuthorization(HttpContextBase httpContext)
    {
        return HttpValidationStatus.Invalid;
    }
    #endregion
}

更新:只是对这一个的快速更新,我从来没有找到一种方法来动态构建我正在检查的角色的名称,从Action名称和控制器名称的组合,仍然在请求的方式和缓存等方面的限制下工作。但是,上面链接的博客上详细介绍的WhiteList授权方法的模式包含在MVC4中。 MVC4目前只是测试版,但我不认为它们会在现在和最终版本之间删除它。