21

Windows AzureMicrosoft.Web.DistributedCache.DistributedCacheOutputCacheProviderを MVC3 アプリの outputCache プロバイダーとして使用する。関連するアクション メソッドは次のとおりです。

[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] 属性を削除すると、期待どおりにビューがキャッシュされます。これは、[Authorize] が必要なアクション メソッドに [OutputCache] を配置できないということですか? または、キャッシュの静的検証コールバック メソッドを使用するカスタム実装で 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 としてアクセスすると、ページの出力がキャッシュされ、ビューに「このページは 2011 年 12 月 31 日 11:06: 12 AM (UTC) にキャッシュされました」と表示されます。その後、サインアウトして another@otherdomain.tld としてサインインし、ページにアクセスすると、「このページは 2011 年 12 月 31 日 11:06: 38 AM (UTC) にキャッシュされました」と表示されます。me@mydomain.tld として再度サインインしてページに再度アクセスすると、キャッシュに「このページは 2011 年 12 月 31 日 11:06: 12 AM (UTC) にキャッシュされました」と再度表示されます。さらにサインイン/サインアウトを試みると、ユーザーに応じて異なる出力がキャッシュされて返されることが示されます。

これにより、出力がユーザーに基づいて個別にキャッシュされていると思われます。これは、VaryByCustom = "User" 設定とオーバーライドの意図です。問題は、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)、これは静的ではありません。静的ではない理由の 1 つは、上記の IMPORTANT コメントで述べたように、 を呼び出すためprotected virtual bool AuthorizeCore(HttpContextBase)です。

静的キャッシュ検証コールバック メソッドから AuthorizeCore ロジックを実行するには、AuthorizeAttribute インスタンスの Users プロパティと Roles プロパティを知る必要があります。ただし、プラグインする簡単な方法はないようです。OnAuthorization をオーバーライドして、これら 2 つの値を HttpContext (Items コレクション?) に入れ、OnCacheAuthorization をオーバーライドしてそれらを元に戻す必要があります。しかし、それは汚いにおいがします。

OutputCache 属性で VaryByCustom = "User" プロパティを慎重に使用する場合、OnCacheAuthorization をオーバーライドして、常に HttpValidationStatus.Valid を返すことはできますか? アクション メソッドに OutputCache 属性がない場合、このコールバックが呼び出されることを心配する必要はありませんよね? また、VaryByCustom = "User" のない OutputCache 属性がある場合、どのユーザー要求がキャッシュされたコピーを作成したかに関係なく、ページがキャッシュされたバージョンを返す可能性があることは明らかです。これはどれほど危険ですか?

4

3 に答える 3

7

この問題に戻ってきて、少しいじくり回した後、Azure DistributedCache を使用する場合、すぐに使用できるものと一緒に使用することはできないという結論に達しました。主な理由は、元の質問のエラー メッセージにあるように、Azure の DistributedCache で使用するには、検証コールバック メソッドを静的にする必要があるためです。MVC Authorize 属性のキャッシュ コールバック メソッドはインスタンス メソッドです。System.Web.Mvc.AuthorizeAttributeSystem.Web.Mvc.OutputCacheAttribute

MVC ソースから AuthorizeAttribute のコピーを作成し、名前を変更し、Azure に接続された OutputCache を使用してアクションに接続し、デバッグすることで、それを機能させる方法を見つけようとしました。キャッシュ コールバック メソッドが静的ではない理由は、属性を承認するために、属性の構築時に設定された 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 を呼び出します。OnAuthorization 中にも呼び出された AuthorizeCore は、主に 3 つのことを行います。

  1. HttpContextBase.User.Identity.IsAuthenticated == true であることを確認します

  2. 属性に空でないユーザー文字列プロパティがある場合、HttpContextBase.User.Identity.Name がコンマ区切り値のいずれかと一致することを確認します。

  3. 属性に空でない Roles 文字列プロパティがある場合は、HttpContextBase.User.IsInRole がコンマ区切り値の 1 つであることを確認します。

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 フィールドにアクセスする必要があるためです。これらのフィールドは、パブリックの Users および Roles プロパティに基づいています。

object data私の最初の試みは、 の引数を使用してこれらの値をコールバックに渡すことでしたCacheValidateHandler。静的メソッドを導入した後でも、これは機能せず、同じ例外が発生しました。オブジェクト データがシリアル化され、コールバック中に検証ハンドラに戻されることを期待していました。どうやらこれは当てはまらず、これを実行しようとすると、Azure の DistributedCache は依然として非静的コールバックと見なし、同じ例外とメッセージが発生します。

// this won't work
cachePolicy.AddValidationCallback(CacheValidateHandler, new object() /* data */);

のインスタンスがハンドラーに自動的に渡されるため、2 回目の試みはHttpContext.Itemsコレクションに値を追加することでした。HttpContextこれもうまくいきませんでした。にHttpContext渡される は、プロパティに存在するCacheValidateHandler インスタンスと同じではありません。filterContext.HttpContext実際、CacheValidateHandler が実行されると、NULL の Session があり、常に空の 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渡されたものには実際には正しい User Principal が含まれています。また、現在 [Authorize] と [OutputCache] を組み合わせたいアクションは、Users または Roles プロパティを AuthorizeAttribute コンストラクターに渡すことはありません。

したがって、これらのプロパティを無視し、User.Identity.IsAuthenticated == true. 特定のロールに対して認証する必要がある場合は、それを行って OutputCache と組み合わせることができます...ただし、キャッシュ検証コールバック メソッドを静的にするために、ロールごとに個別の属性が必要になります。 . 少し磨いたら、戻ってきてコードを投稿します。

于 2012-05-12T12:26:53.377 に答える
2

あなたは正しいオリーブです。キャッシングは、アクションの出力全体 (すべての属性を含む) をキャッシュし、実際にコードを呼び出すことなく、後続の呼び出しに結果を返すことによって機能します。

このため、キャッシュして承認を確認することはできません。これは、キャッシュによってコード (承認を含む) を呼び出さないためです。したがって、キャッシュされているものはすべて公開されている必要があります。

于 2011-12-30T20:39:23.717 に答える