MVC 5:自定义授权属性和缓存

本文关键字:属性 缓存 授权 自定义 MVC | 更新日期: 2023-09-27 18:28:09

我正在尝试通过从自定义System.Web.Mvc.AuthorizeAttribute派生并覆盖其某些方法来找到实现自定义的解决方案。
我正在尝试的每种方法,我都面临着 MVC 5 的默认授权机制中的某些问题,这些问题阻止了我正确扩展它。
我已经在SO和许多专用资源上对这个领域进行了大量研究,但是对于像我目前这样的场景,我无法获得可靠的解决方案。

第一个限制
我的授权逻辑需要其他数据,例如应用于它们的控制器和方法名称和属性,而不是HttpContextBase能够提供的有限数据部分。
例:

public override void OnAuthorization(AuthorizationContext filterContext)
{
    ...
    var actionDescriptor = filterContext.ActionDescriptor;
    var currentAction = actionDescriptor.ActionName;
    var currentController = actionDescriptor.ControllerDescriptor.ControllerName;
    var hasHttpPostAttribute = actionDescriptor.GetCustomAttributes(typeof(HttpPostAttribute), true).Any();
    var hasHttpGetAttribute = actionDescriptor.GetCustomAttributes(typeof(HttpGetAttribute), true).Any();
    var isAuthorized = securitySettingsProvider.IsAuthorized(
        currenPrincipal, currentAction, currentController, hasHttpPostAttribute, hasHttpGetAttribute);
    ...
}

这就是为什么我无法在 AuthorizeCore() 方法覆盖中实现我的授权逻辑,因为它只HttpContextBase作为参数,我需要做出授权决策的是AuthorizationContext
这导致我将我的授权逻辑置于OnAuthorization()方法覆盖,如上例所示。

但这里我们来到第二个限制
AuthorizeCore()方法由缓存系统调用,以做出授权决定是应使用缓存ActionResult处理当前请求,还是应使用相应的控制器方法来创建新ActionResult
因此,我们不能只是忘记AuthorizeCore()而只使用OnAuthorization()

在这里,我们回到了起点:
只有当我们需要来自AuthorizationContext的更多数据时,如何根据HttpContextBase对缓存系统做出授权决策
有许多后续问题,例如:

  • 我们应该如何正确实施AuthorizeCore()这个案子?
  • 我是否应该实现自己的缓存以让它提供授权系统有足够的数据?以及如何做到这一点如果是?
  • 或者我应该告别所有控制器的缓存使用我的自定义System.Web.Mvc.AuthorizeAttribute保护的方法?这里必须说,我将使用我的自定义System.Web.Mvc.AuthorizeAttribute作为全局过滤器,这是完全告别缓存,如果答案问题是肯定的。

所以这里的主要问题:
处理此类自定义授权和正确缓存的可能方法是什么?

更新 1(解决一些可能答案的其他信息(:

  1. 在 MVC 中无法保证 AuthorizeAttribute将处理单个请求。它可以重复使用对于许多请求(请参阅此处获取更多信息(:

    操作筛选器属性必须是不可变的,因为它们可能被缓存 由部分管道重复使用。取决于此属性的位置 在您的应用程序中声明,这将打开定时攻击,该攻击 然后,恶意网站访问者可以利用此漏洞授予自己访问 他想要的任何行动。

    换句话说,AuthorizeAttribute必须是不可变的,并且不得在任何方法调用之间共享状态
    此外,在 AuthorizeAttribute -as-global-filter 方案,单个实例AuthorizeAttribute用于处理所有请求。
    如果您认为您在请求的OnAuthorization()中保存了AuthorizationContext,然后您可以在后续AuthorizeCore()中为同一请求获取它,那么您错了。
    因此,您将根据来自其他请求的AuthorizationContext对当前请求做出授权决策。

  2. 如果AuthorizeCore()是由缓存层触发的,OnAuthorization()以前从未为当前请求调用过(请参考从CacheValidateHandler()AuthorizeCore() AuthorizeAttribute的来源(。
    换句话说,如果要使用缓存ActionResult来服务请求,则只会调用AuthorizeCore()而不是OnAuthorization()
    因此,在这种情况下,无论如何您都无法保存AuthorizationContext

因此,在OnAuthorization()AuthorizeCore()之间共享AuthorizationContext不是选择!

MVC 5:自定义授权属性和缓存

AuthorizeCore 方法之前调用 OnAuthorization 方法。因此,您可以保存当前上下文以供以后处理:

public class MyAttribute: AuthorizeAttribute
{
    # Warning - this code doesn't work - see comments
    private AuthorizationContext _currentContext;
    public override void OnAuthorization(AuthorizationContext filterContext)
    {
         _currentContext = filterContext;
         base.OnAuthorization(filterContext);
    }
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
         // use _currentContext
    }    
}

编辑

因为正如亚历山大指出的那样,这行不通。第二个选项可能是完全覆盖 OnAuthorization 方法:

        public override void OnAuthorization(AuthorizationContext filterContext)
        {
            if (filterContext == null)
            {
                throw new ArgumentNullException("filterContext");
            }
            if (OutputCacheAttribute.IsChildActionCacheActive(filterContext))
            {
                throw new InvalidOperationException(MvcResources.AuthorizeAttribute_CannotUseWithinChildActionCache);
            }
            bool skipAuthorization = filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), inherit: true)
                                     || filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), inherit: true);
            if (skipAuthorization)
            {
                return;
            }
            if (AuthorizeCore(filterContext.HttpContext))
            {
                HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
                cachePolicy.SetProxyMaxAge(new TimeSpan(0));
                var actionDescriptor = filterContext.ActionDescriptor;
                var currentAction = actionDescriptor.ActionName;
                var currentController = actionDescriptor.ControllerDescriptor.ControllerName;
                var hasHttpPostAttribute = actionDescriptor.GetCustomAttributes(typeof(HttpPostAttribute), true).Any();
                var hasHttpGetAttribute = actionDescriptor.GetCustomAttributes(typeof(HttpGetAttribute), true).Any();
                // fill the data parameter which is null by default
                cachePolicy.AddValidationCallback(CacheValidateHandler, new { actionDescriptor : actionDescriptor, currentAction: currentAction, currentController: currentController, hasHttpPostAttribute : hasHttpPostAttribute, hasHttpGetAttribute: hasHttpGetAttribute  });
            }
            else
            {
                HandleUnauthorizedRequest(filterContext);
            }
        }
    private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
    {
        if (httpContext == null)
        {
            throw new ArgumentNullException("httpContext");
        }
        // the data will contain AuthorizationContext attributes
        bool isAuthorized = myAuthorizationLogic(httpContext, data);
        return (isAuthorized) ? HttpValidationStatus.Valid : httpValidationStatus.IgnoreThisRequest;
    }