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

Asp.net mvc 有关使用重写的AuthorizeAttribute、线程安全、ChildActions和缓存的MVC3自定义安全设置的问题,asp.net-mvc,asp.net-mvc-3,caching,thread-safety,security,Asp.net Mvc,Asp.net Mvc 3,Caching,Thread Safety,Security,因此,在为我的MVC3应用程序寻找一个健壮的安全解决方案后,我遇到了Rick Anderson。它详细介绍了一种白名单方法,其中AuthorizeAttribute的自定义实现被应用为全局过滤器,并且您装饰了您希望使用伪属性AllowAnonymousAttribute允许匿名访问的操作/控制器(我说伪是因为AllowAnonymousAttribute中没有逻辑,它只是一个空属性类) 这(以及他在博客中提到的其他安全建议,如HTTPS)为我提供了一个默认安全模型,我不必对每一个操作都进行安全检

因此,在为我的MVC3应用程序寻找一个健壮的安全解决方案后,我遇到了Rick Anderson。它详细介绍了一种白名单方法,其中AuthorizeAttribute的自定义实现被应用为全局过滤器,并且您装饰了您希望使用伪属性AllowAnonymousAttribute允许匿名访问的操作/控制器(我说伪是因为AllowAnonymousAttribute中没有逻辑,它只是一个空属性类)

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

问题的第一部分

现在,我没有使用authorized属性上的Users/Roles属性,我需要从数据库中获取这些内容。对我来说,这是AuthorizeCore中的东西,因为它的唯一责任是返回一个true-false,用户是否有访问权限。但是我有一个问题,根据我对AuthorizeAttribute类的源代码的阅读,AuthorizeCore必须是线程安全的,我不确定访问数据库以确定用户权限并遵守该权限的最佳方法。我的应用程序正在使用IoC,并且目前允许我的IoC容器将我的存储库注入AuthorizeAttribute的构造函数,但通过这样做,然后在AuthorizeCore中访问它,我不会导致线程安全问题吗?或者,IoC实现和MVC3 DependencyResolver(我用来向自定义AuthorizeAttribute构造函数提供参数)能否充分处理线程安全问题?注意,我的存储库使用的是UnitOfWork模式,其中包括我的nHibernate SessionFactory作为存储库的构造函数,并且工作类的单元是从我的IoC容器中提供的,由StructureMap使用下面的行实现的。我认为这里使用的范围将处理线程问题的想法正确吗

For<IUnitOfWork>().HybridHttpOrThreadLocalScoped().Use<UnitOfWork>();
这样做有意义吗?如果有/没有,为什么?在我看来,如果这个动作是用ChildActionOnlyAttribute修饰的,那么无论如何,它都不能通过URL公开访问。如果它是作为子操作执行的,但不是唯一的子操作,我可以跳过安全检查,因为父操作将处理权限。您是否曾经遇到过需要限制访问儿童活动的情况?知道子操作通常是非常小的局部视图,我并不认为这是一个问题,但我也看到了OnAuthorization的默认实现中的一行参考,其中概述了缓存方面的一些问题。有人知道这是否会影响我提出的解决方案吗

简要关注事项:

  • 多线程问题 从访问用户权限 核心内的数据库
  • 绕过安全问题 子操作的授权检查
  • 缓存子操作的关注点 结合上一点

如果您对这些方面有任何意见或帮助,我们将不胜感激

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

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

肯定有一个更好的方法来处理这个问题,它没有那么重。如果在一个单独的权限请求中多次访问db,则需要通过某种机制(自定义或实现另一个基于声明的系统等)缓存权限集

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

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


此外,如果您不能为用户缓存整个树,那么您当然可以在对AuthorizeCore的第一次调用中缓存安全检查。后续调用只需检查前缓存角色——如果存在,则使用它们,否则查看数据库

到目前为止,这就是我所处的位置。我觉得我已经把它搞砸了,但我不知道还有什么别的办法可以满足我想要满足的要求。我是完全错了还是只是在我的脑子里

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
}

更新:这只是一个快速更新,我从来没有找到一种方法,通过操作名称和控制器名称的组合动态生成我正在检查的角色的名称,并且仍然在请求和缓存方式的限制范围内工作,然而,MVC4中包含了我在上面链接的博客中详细介绍的白名单授权模式。MVC4目前只是测试版,但我不希望从现在到最终版本之间他们会删除它。

这些都是很好的问题。谢谢你的邀请;我也对回答这个问题感兴趣。我希望有人能帮助我们。我的授权逻辑相当“重”,当我看到它连续运行5次时,我会感到害怕,因为1个HttpGet/Post/Delete,然后是4个后续的ChildActions。安全性与其说是路由本身,不如说是路由值。我正在使用控制器的名称和操作的名称作为我的服务器上的标识符
bool bypassChildAction = filterContext.ActionDescriptor.IsDefined(typeof (ChildActionOnlyAttribute), true) || filterContext.IsChildAction;
if (bypassChildAction) return;
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
}