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(解决一些可能答案的其他信息(:
在 MVC 中无法保证
AuthorizeAttribute
将处理单个请求。它可以重复使用对于许多请求(请参阅此处获取更多信息(:操作筛选器属性必须是不可变的,因为它们可能被缓存 由部分管道重复使用。取决于此属性的位置 在您的应用程序中声明,这将打开定时攻击,该攻击 然后,恶意网站访问者可以利用此漏洞授予自己访问 他想要的任何行动。
换句话说,
AuthorizeAttribute
必须是不可变的,并且不得在任何方法调用之间共享状态。
此外,在AuthorizeAttribute
-as-global-filter 方案,单个实例AuthorizeAttribute
用于处理所有请求。
如果您认为您在请求的OnAuthorization()
中保存了AuthorizationContext
,然后您可以在后续AuthorizeCore()
中为同一请求获取它,那么您错了。
因此,您将根据来自其他请求的AuthorizationContext
对当前请求做出授权决策。- 如果
AuthorizeCore()
是由缓存层触发的,OnAuthorization()
以前从未为当前请求调用过(请参考从CacheValidateHandler()
到AuthorizeCore()
AuthorizeAttribute
的来源(。
换句话说,如果要使用缓存ActionResult
来服务请求,则只会调用AuthorizeCore()
而不是OnAuthorization()
。
因此,在这种情况下,无论如何您都无法保存AuthorizationContext
。
因此,在OnAuthorization()
和AuthorizeCore()
之间共享AuthorizationContext
不是选择!
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;
}