MVC 5:自定义AuthorizeAttribute和缓存

时间:2014-10-28 10:15:48

标签: c# asp.net .net asp.net-mvc caching

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

第一个限制
我的授权逻辑需要其他数据,例如控制器和方法名称以及应用于它们的属性,而不是数据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()并仅使用HttpContextBase

在这里,我们回到初始点:
仅当我们需要AuthorizationContext 中的更多数据时,如何根据AuthorizeCore()对缓存系统做出授权决策?

随后有许多问题,如:

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

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

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

  1. MVC中没有保证每一个实例 AuthorizeAttribute将提供单个请求。它可以重复使用 对于许多要求(见 here了解更多信息):

      

    动作过滤器属性必须是不可变的,因为它们可能被缓存   通过部分管道并重复使用。取决于此属性的位置   在您的应用程序中声明,这会打开一个计时攻击,其中a   恶意网站访问者然后可以利用来授予自己访问权限   他希望的任何行动。

    换句话说,AuthorizeAttribute 必须是不可变的不得在任何方法调用之间共享状态 而且在 AuthorizeAttribute - 全局过滤方案,单个实例 AuthorizationContext用于处理所有请求 如果您认为在OnAuthorization()中为AuthorizeCore()保存了AuthorizationContext,那么您可以在后续的AuthorizeCore()中获取相同的请求,那就错了。 因此,您将根据其他请求中的OnAuthorization()对当前请求作出授权决定。

  2. 如果缓存层触发了AuthorizeAttribute,则CacheValidateHandler()之前从未调用当前请求(请AuthorizeCore() ActionResult从{{{{1}开始1}}下至AuthorizeCore())。
    换句话说,如果要使用缓存的OnAuthorization()投放请求,则只会调用AuthorizationContext,而不会调用AuthorizationContext。 因此,在这种情况下,您无法保存OnAuthorization()
  3. 因此,在AuthorizeCore()和{{1}}之间共享{{1}}不是必须的选择!

1 个答案:

答案 0 :(得分: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;

    }