为什么在使用 Azure 缓存(.NET MVC3 应用程序)时不能组合 [Authorize] 和 [OutputCache] 属性? [英] Why can't I combine [Authorize] and [OutputCache] attributes when using Azure cache (.NET MVC3 app)?

查看:21
本文介绍了为什么在使用 Azure 缓存(.NET MVC3 应用程序)时不能组合 [Authorize] 和 [OutputCache] 属性?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

使用 Windows Azure 的 Microsoft.Web.DistributedCache.DistributedCacheOutputCacheProvider 作为 MVC3 应用程序的 outputCache 提供程序.下面是相关的操作方法:

Using Windows Azure's Microsoft.Web.DistributedCache.DistributedCacheOutputCacheProvider as the outputCache provider for an MVC3 app. Here is the relevant action method:

[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();
}

从网络浏览器加载此视图时出现以下异常:

I get the following exception when loading this view from a web browser:

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?

If I remove the [Authorize] attribute, the view caches as would be expected. Does this mean I cannot put [OutputCache] on an action method that must have [Authorize]? Or, do I need to override AuthorizeAttribute with a custom implementation that uses a static validation callback method for the cache?

更新 1

在Evan的回答之后,我在IIS Express(Azure之外)测试了上面的action方法.这是我对 OutputCache 属性上的 VaryByCustom = "User" 属性的覆盖:

After Evan's answer, I tested the above action method in IIS Express (outside of Azure). Here is my override for the VaryByCustom = "User" property on the OutputCache attribute:

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 was 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) 缓存".进一步的登录/退出尝试表明正在缓存不同的输出根据用户返回.

When I visit the sample cached page as me@mydomain.tld, the output of the page is cached, and the view displays "This page was cached at 12/31/2011 11:06:12 AM (UTC)". If I then sign out and sign in as another@otherdomain.tld and visit the page, it displays "This page was cached at 12/31/2011 11:06:38 AM (UTC)". Signing back in as me@mydomain.tld and revisiting the page causes the cache to display "This page was cached at 12/31/2011 11:06:12 AM (UTC)" again. Further sign in/out attempts show that different output is being cached & returned depending on the user.

这让我相信输出是根据用户单独缓存的,这是我的 VaryByCustom = "User" 设置的意图 &覆盖.问题是它不适用于 Azure 的分布式缓存提供程序.埃文,你是否回答说只缓存公共内容仍然有效?

This is leading me to believe that the output is being cached separately based on the user, which is the intention with my VaryByCustom = "User" setting & override. The problem is that it doesn't work with Azure's distributed cache provider. Evan, does you answer about only caching public content still stand?

更新 2

我翻了一下源码,发现开箱即用的 AuthorizeAttribute 确实有一个非静态验证回调.以下是 OnAuthorization 的摘录:

I dug up the source, and found that the out-of-box AuthorizeAttribute does in fact have a non-static validation callback. Here is an excerpt from 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).

CacheValidationHandler delegates the cache validation to protected virtual HttpValidationStatus OnCacheAuthorization(HttpContextBase), which of course is not static. One reason why it is not static is because, as noted in the IMPORTANT comment above, it invokes protected virtual bool AuthorizeCore(HttpContextBase).

为了从静态缓存验证回调方法执行任何 AuthorizeCore 逻辑,它需要知道 AuthorizeAttribute 实例的 Users 和 Roles 属性.但是,似乎没有一种简单的方法可以插入.我必须覆盖 OnAuthorization 才能将这 2 个值放入 HttpContext(Items 集合?),然后覆盖 OnCacheAuthorization 以将它们取出.但这闻起来很脏.

In order to do any of the AuthorizeCore logic from a static cache validation callback method, it would need to know the Users and Roles properties of the AuthorizeAttribute instance. However there doesn't seem to be an easy way to plug in. I would have to override OnAuthorization to put these 2 values into the HttpContext (Items collection?) and then override OnCacheAuthorization to get them back out. But that smells dirty.

如果我们小心地使用 OutputCache 属性中的 VaryByCustom = "User" 属性,我们是否可以只覆盖 OnCacheAuthorization 以始终返回 HttpValidationStatus.Valid?当 action 方法没有 OutputCache 属性时,我们不需要担心这个回调是否会被调用,对吗?如果我们确实有一个没有 VaryByCustom = "User" 的 OutputCache 属性,那么很明显,无论哪个用户请求创建了缓存副本,页面都可以返回任何缓存版本.这有多大风险?

If we are careful to use the VaryByCustom = "User" property in the OutputCache attribute, can we just override OnCacheAuthorization to always return HttpValidationStatus.Valid? When the action method does not have an OutputCache attribute, we would not need to worry about this callback ever being invoked, correct? And if we do have an OutputCache attribute without VaryByCustom = "User", then it should be obvious that the page could return any cached version regardless of which user request created the cached copy. How risky is this?

推荐答案

缓存发生在 Action 之前.您可能需要自定义授权机制来处理缓存场景.

Caching happens before the Action. You will likely need to customize your authorization mechanics to handle cache scenarios.

查看我不久前发布的一个问题 - MVC 自定义身份验证、授权和角色实现.

Check out a question I posted a while back - MVC Custom Authentication, Authorization, and Roles Implementation.

我认为对您有帮助的部分是自定义授权属性,它的 OnAuthorize() 方法处理缓存.

The part I think would help you is a custom Authorize Attribute who's OnAuthorize() method deals with caching.

下面是一个代码块,例如:

Below is a code block for example:

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

这篇关于为什么在使用 Azure 缓存(.NET MVC3 应用程序)时不能组合 [Authorize] 和 [OutputCache] 属性?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆