首页 文章

MVC 5:自定义 AuthorizeAttribute 和缓存

提问于
浏览
10

我试图找到一个解决方案来实现自定义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()

  • 我应该实现自己的缓存,以便为授权系统提供足够的数据吗?如果可以的话怎么做?

  • 或者我应该说 good-bye 缓存所有受我的自定义System.Web.Mvc.AuthorizeAttribute保护的控制器方法?这里必须说我将使用我的自定义System.Web.Mvc.AuthorizeAttribute作为全局过滤器,如果这个问题的答案是肯定的,这就是缓存的完整 good-bye。

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

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

  • MVC 中没有任何保证,AuthorizeAttribute的每个实例都将提供单个请求。它可以重复用于许多请求(有关详细信息,请参阅这里):

操作过滤器属性必须是不可变的,因为它们可能被管道的某些部分缓存并重新使用。根据应用程序中声明此属性的位置,这会打开一个计时攻击,然后恶意网站访问者可以利用该攻击授予自己访问他希望的任何操作的权限。

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

  • 如果缓存层触发AuthorizeCore(),则OnAuthorization()从未为当前请求调用过(请将的来源CacheValidateHandler()开始引用至AuthorizeCore())。
    换句话说,如果要使用缓存的ActionResult来提供请求,则只调用AuthorizeCore()而不调用OnAuthorization()
    所以在这种情况下你无法保存AuthorizationContext

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

1 回答

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

相关问题