使用Windows Azure的Microsoft.Web.DistributedCache.DistributedCacheOutputCacheProvider
作为MVC3应用的outputCache提供程序。下面是相关的action方法:
[ActionName("sample-cached-page")]
[OutputCache(Duration = 300, VaryByCustom = "User",
Location = OutputCacheLocation.Server)]
[Authorize(Users = "me@mydomain.tld,another@otherdomain.tld")]
public virtual ActionResult SampleCachedPage()
{
return View();
}
从web浏览器加载此视图时,出现以下异常:
System.Configuration.Provider.ProviderException: When using a custom output cache provider like 'DistributedCache', only the following expiration policies and cache features are supported: file dependencies, absolute expirations, static validation callbacks and static substitution callbacks.
System.Configuration.Provider.ProviderException: When using a custom output cache provider like 'DistributedCache', only the following expiration policies and cache features are supported: file dependencies, absolute expirations, static validation callbacks and static substitution callbacks.
at System.Web.Caching.OutputCache.InsertResponse(String cachedVaryKey, CachedVary cachedVary, String rawResponseKey, CachedRawResponse rawResponse, CacheDependency dependencies, DateTime absExp, TimeSpan slidingExp)
at System.Web.Caching.OutputCacheModule.OnLeave(Object source, EventArgs eventArgs)
at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
如果我删除Authorize属性,视图将按照预期进行缓存。这是否意味着我不能将OutputCache放在必须具有Authorize的操作方法上?或者,我是否需要使用对缓存使用静态验证回调方法的自定义实现来覆盖AuthorizeAttribute?
更新1
在Evan的回答之后,我在IIS Express中(Azure之外)测试了上面的操作方法。下面是我对OutputCache属性的VaryByCustom = "User“属性的覆盖:
public override string GetVaryByCustomString(HttpContext context, string custom)
{
return "User".Equals(custom, StringComparison.OrdinalIgnoreCase)
? Thread.CurrentPrincipal.Identity.Name
: base.GetVaryByCustomString(context, custom);
}
当我作为me@mydomain.tld访问示例缓存页面时,页面的输出被缓存,视图显示"This page is cached at 12/31/2011 11:06:12 AM (UTC)“。如果我随后注销并以another@otherdomain.tld身份登录并访问该页面,则会显示“此页面已缓存于12/31/2011 11:06:38 AM (UTC)”。以me@mydomain.tld身份重新登录并重新访问该页面会导致缓存再次显示“此页面缓存于12/31/2011 11:06:12 AM (UTC)”。进一步的登录/注销尝试显示,根据用户的不同,缓存和返回不同的输出。
这让我相信输出是根据用户单独缓存的,这就是我的VaryByCustom = " user“设置& override的目的。问题是它不能与Azure的分布式缓存提供程序一起工作。Evan,你的答案是只缓存公共内容仍然有效吗?
更新2
我找出了源代码,发现开箱即用的AuthorizeAttribute确实有一个非静态的验证回调。以下是OnAuthorization
的摘录
if (AuthorizeCore(filterContext.HttpContext)) {
// ** IMPORTANT **
// Since we're performing authorization at the action level, the authorization code runs
// after the output caching module. In the worst case this could allow an authorized user
// to cause the page to be cached, then an unauthorized user would later be served the
// cached page. We work around this by telling proxies not to cache the sensitive page,
// then we hook our custom authorization code into the caching mechanism so that we have
// the final say on whether a page should be served from the cache.
HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge(new TimeSpan(0));
cachePolicy.AddValidationCallback(CacheValidateHandler, null /* data */);
}
else {
HandleUnauthorizedRequest(filterContext);
}
CacheValidationHandler
将缓存验证委托给protected virtual HttpValidationStatus OnCacheAuthorization(HttpContextBase)
,它当然不是静态的。它不是静态的原因之一是,正如上面的重要注释所指出的,它调用了protected virtual bool AuthorizeCore(HttpContextBase)
。
为了从静态缓存验证回调方法执行任何AuthorizeCore逻辑,它需要知道AuthorizeAttribute实例的用户和角色属性。然而,似乎没有一种简单的方法来插入。我必须重写OnAuthorization才能将这两个值放入HttpContext (Items集合?)然后重写OnCacheAuthorization把它们弄回来。但那闻起来很脏。
如果我们小心地在OutputCache属性中使用VaryByCustom = "User“属性,我们是否可以只覆盖OnCacheAuthorization以始终返回HttpValidationStatus.Valid?当操作方法没有OutputCache属性时,我们就不需要担心这个回调被调用了,对吗?如果我们有一个没有VaryByCustom = " user“的OutputCache属性,那么很明显,页面可以返回任何缓存的版本,而不管是哪个用户请求创建了缓存的副本。这有多大的风险?
发布于 2012-01-07 03:45:44
缓存发生在Action之前。您可能需要自定义授权机制来处理缓存场景。
看看我前段时间发布的一个问题-- MVC Custom Authentication, Authorization, and Roles Implementation。
我认为对您有帮助的部分是自定义Authorize属性,该属性由OnAuthorize()
方法处理缓存。
下面是一个示例代码块:
/// <summary>
/// Uses injected authorization service to determine if the session user
/// has necessary role privileges.
/// </summary>
/// <remarks>As authorization code runs at the action level, after the
/// caching module, our authorization code is hooked into the caching
/// mechanics, to ensure unauthorized users are not served up a
/// prior-authorized page.
/// Note: Special thanks to TheCloudlessSky on StackOverflow.
/// </remarks>
public void OnAuthorization(AuthorizationContext filterContext)
{
// User must be authenticated and Session not be null
if (!filterContext.HttpContext.User.Identity.IsAuthenticated || filterContext.HttpContext.Session == null)
HandleUnauthorizedRequest(filterContext);
else {
// if authorized, handle cache validation
if (_authorizationService.IsAuthorized((UserSessionInfoViewModel)filterContext.HttpContext.Session["user"], _authorizedRoles)) {
var cache = filterContext.HttpContext.Response.Cache;
cache.SetProxyMaxAge(new TimeSpan(0));
cache.AddValidationCallback((HttpContext context, object o, ref HttpValidationStatus status) => AuthorizeCache(context), null);
}
else
HandleUnauthorizedRequest(filterContext);
}
}
/// <summary>
/// Ensures that authorization is checked on cached pages.
/// </summary>
/// <param name="httpContext"></param>
/// <returns></returns>
public HttpValidationStatus AuthorizeCache(HttpContext httpContext)
{
if (httpContext.Session == null)
return HttpValidationStatus.Invalid;
return _authorizationService.IsAuthorized((UserSessionInfoViewModel) httpContext.Session["user"], _authorizedRoles)
? HttpValidationStatus.Valid
: HttpValidationStatus.IgnoreThisRequest;
}
发布于 2012-05-12 20:26:53
我又回到了这个问题上,在经过一些修补之后,我得出的结论是,当使用Azure System.Web.Mvc.AuthorizeAttribute
时,你不能同时使用开箱即用的System.Web.Mvc.OutputCacheAttribute
和开箱即用的DistributedCache。主要原因是,正如原始问题中的错误消息所述,验证回调方法必须是静态的,才能与Azure的DistributedCache一起使用。MVC Authorize属性中的缓存回调方法是一个实例方法。
我试着弄清楚如何让它工作,从MVC源码复制一个AuthorizeAttribute,重命名它,将它挂接到一个OutputCache连接到Azure的操作上,并进行调试。缓存回调方法不是静态的原因是,为了授权,该属性需要根据构造该属性时设置的Users和Roles属性值检查HttpContext的User。相关代码如下:
OnAuthorization
public virtual void OnAuthorization(AuthorizationContext filterContext) {
//... code to check argument and child action cache
if (AuthorizeCore(filterContext.HttpContext)) {
// Since we're performing authorization at the action level,
// the authorization code runs after the output caching module.
// In the worst case this could allow an authorized user
// to cause the page to be cached, then an unauthorized user would
// later be served the cached page. We work around this by telling
// proxies not to cache the sensitive page, then we hook our custom
// authorization code into the caching mechanism so that we have
// the final say on whether a page should be served from the cache.
HttpCachePolicyBase cachePolicy = filterContext
.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge(new TimeSpan(0));
cachePolicy.AddValidationCallback(CacheValidateHandler, null /* data */);
}
else {
HandleUnauthorizedRequest(filterContext);
}
}
缓存验证回调
private void CacheValidateHandler(HttpContext context, object data,
ref HttpValidationStatus validationStatus) {
validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
}
// This method must be thread-safe since it is called by the caching module.
protected virtual HttpValidationStatus OnCacheAuthorization
(HttpContextBase httpContext) {
if (httpContext == null) {
throw new ArgumentNullException("httpContext");
}
bool isAuthorized = AuthorizeCore(httpContext);
return (isAuthorized)
? HttpValidationStatus.Valid
: HttpValidationStatus.IgnoreThisRequest;
}
正如您所看到的,缓存验证回调最终调用AuthorizeCore,这是另一个实例方法(protected virtual)。在OnAuthorization期间也调用了AuthorizeCore,它做了3件主要的事情:
AuthorizeCore
// This method must be thread-safe since it is called by the thread-safe
// OnCacheAuthorization() method.
protected virtual bool AuthorizeCore(HttpContextBase httpContext) {
if (httpContext == null) {
throw new ArgumentNullException("httpContext");
}
IPrincipal user = httpContext.User;
if (!user.Identity.IsAuthenticated) {
return false;
}
if (_usersSplit.Length > 0 && !_usersSplit.Contains
(user.Identity.Name, StringComparer.OrdinalIgnoreCase)) {
return false;
}
if (_rolesSplit.Length > 0 && !_rolesSplit.Any(user.IsInRole)) {
return false;
}
return true;
}
当您简单地尝试将验证回调方法设为静态时,代码将不会编译,因为它需要访问这些基于公共用户和角色属性的_rolesSplit和_usersSplit字段。
我的第一个尝试是使用CacheValidateHandler
的object data
参数将这些值传递给回调。即使在引入静态方法之后,这仍然不起作用,并导致相同的异常。我希望对象数据将被序列化,然后在回调期间传递回验证处理程序。显然情况并非如此,当你尝试这样做时,Azure的DistributedCache仍然认为它是一个非静态回调,导致相同的异常&消息。
// this won't work
cachePolicy.AddValidationCallback(CacheValidateHandler, new object() /* data */);
我的第二个尝试是将值添加到HttpContext.Items
集合,因为HttpContext
的一个实例会自动传递给处理程序。这也不管用。传递给CacheValidateHandler
的HttpContext
与filterContext.HttpContext
属性上存在的实例不同。实际上,当CacheValidateHandler执行时,它有一个空会话,并且总是有一个空的Items集合。
// this won't work
private void CacheValidateHandler(HttpContext context, object data,
ref HttpValidationStatus validationStatus) {
Debug.Assert(!context.Items.Any()); // even after I put items into it
validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
}
然而..。
尽管似乎没有办法将Users & Roles属性值传递回缓存验证回调处理程序,但传递给它的HttpContext
实际上具有正确的用户主体。此外,我目前希望组合Authorize和OutputCache的操作都不会向AuthorizeAttribute构造函数传递用户或角色属性。
因此,可以创建一个忽略这些属性的自定义AuthenticateAttribute,并且只进行检查以确保User.Identity.IsAuthenticated == true
。如果您需要针对特定角色进行身份验证,您也可以这样做并与OutputCache结合使用……但是,为了使缓存验证回调方法成为静态方法,您需要为每个(一组)角色指定一个不同的属性。我会回来发布我的代码后,我已经打磨了一点。
发布于 2011-12-31 04:39:24
你是对的奥利弗。缓存的工作原理是缓存Action的整个输出(包括所有属性),然后将结果返回给后续调用,而无需实际调用任何代码。
因此,您无法缓存和检查授权,因为通过缓存,您将不会调用任何代码(包括授权)。因此,缓存的任何内容都必须是公共的。
https://stackoverflow.com/questions/8682635
复制相似问题