2011-12-30 32 views
18

Sử dụng Windows23's Microsoft.Web.DistributedCache.DistributedCacheOutputCacheProvider làm nhà cung cấp outputCache cho một ứng dụng MVC3. Dưới đây là phương pháp hành động có liên quan:Tại sao tôi không thể kết hợp các thuộc tính [Authorize] và [OutputCache] khi sử dụng bộ đệm ẩn Azure (ứng dụng .NET MVC3)?

[ActionName("sample-cached-page")] 
[OutputCache(Duration = 300, VaryByCustom = "User", 
    Location = OutputCacheLocation.Server)] 
[Authorize(Users = "[email protected],[email protected]")] 
public virtual ActionResult SampleCachedPage() 
{ 
    return View(); 
} 

tôi nhận được ngoại lệ sau khi tải quan điểm này từ trình duyệt 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) 

Nếu tôi loại bỏ các [Duyệt] thuộc tính, các cache xem như sẽ là kỳ vọng. Điều này có nghĩa là tôi không thể đặt [OutputCache] trên một phương thức hành động phải có [Authorize]? Hoặc, tôi có cần ghi đè AuthorizeAttribute với một triển khai tùy chỉnh sử dụng phương thức gọi lại xác thực tĩnh cho bộ đệm không?

Cập nhật 1

Sau câu trả lời Evan, tôi đã thử nghiệm phương pháp hành động nêu trên trong IIS Express (bên ngoài Azure). Dưới đây là ghi đè lên tôi cho VaryByCustom = "Thành viên" tài sản trên thuộc tính OutputCache:

public override string GetVaryByCustomString(HttpContext context, string custom) 
{ 
    return "User".Equals(custom, StringComparison.OrdinalIgnoreCase) 
     ? Thread.CurrentPrincipal.Identity.Name 
     : base.GetVaryByCustomString(context, custom); 
} 

Khi tôi truy cập vào trang mẫu được lưu trữ như [email protected], sản lượng của trang web được lưu trữ, và các màn hình xem "Trang này đã được lưu vào bộ nhớ cache lúc 12/31/2011 11:06: AM (UTC)". Nếu sau đó tôi đăng xuất và đăng nhập bằng [email protected] và truy cập trang, nó sẽ hiển thị "Trang này đã được lưu vào bộ nhớ cache lúc 12/31/2011 11:06: AM (UTC)". Đăng nhập lại với tên [email protected] và truy cập lại trang sẽ khiến bộ nhớ cache hiển thị "Trang này được lưu vào bộ nhớ cache lúc 12/31/2011 11:06: AM (UTC)" một lần nữa. Các nỗ lực đăng nhập/đăng xuất khác cho thấy rằng đầu ra khác nhau đang được lưu vào bộ nhớ cache & được trả lại tùy thuộc vào người dùng.

Điều này khiến tôi tin rằng đầu ra được lưu trữ riêng biệt dựa trên người dùng, đó là ý định với cài đặt VaryByCustom = "Người dùng" của tôi & ghi đè. Vấn đề là nó không hoạt động với nhà cung cấp bộ nhớ cache phân phối của Azure. Evan, bạn có trả lời về chỉ bộ nhớ đệm nội dung công cộng vẫn còn đứng?

Cập nhật 2

tôi đào lên nguồn, và thấy rằng các out-of-box AuthorizeAttribute nào trong thực tế có một callback xác nhận không tĩnh. Dưới đây là một đoạn trích từ 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 đại biểu xác nhận bộ nhớ cache để protected virtual HttpValidationStatus OnCacheAuthorization(HttpContextBase), trong đó tất nhiên không phải là tĩnh. Một lý do tại sao nó không phải là tĩnh là bởi vì, như đã lưu ý trong chú thích QUAN TRỌNG ở trên, nó gọi protected virtual bool AuthorizeCore(HttpContextBase).

Để thực hiện bất kỳ logic AuthorizeCore nào từ phương thức gọi lại xác thực bộ nhớ cache tĩnh, nó sẽ cần phải biết thuộc tính Người dùng và Vai trò của cá thể AuthorizeAttribute. Tuy nhiên có vẻ không phải là một cách dễ dàng để cắm vào. Tôi sẽ phải ghi đè OnAuthorization để đặt 2 giá trị này vào HttpContext (Items collection?) Và sau đó ghi đè OnCacheAuthorization để đưa chúng trở lại. Nhưng có mùi bẩn.

Nếu chúng ta cẩn thận khi sử dụng thuộc tính VaryByCustom = "User" trong thuộc tính OutputCache, chúng ta có thể ghi đè OnCacheAuthorization để luôn trả về HttpValidationStatus.Valid không?Khi phương thức action không có thuộc tính OutputCache, chúng ta sẽ không cần phải lo lắng về việc gọi lại bao giờ được gọi, đúng không? Và nếu chúng ta có thuộc tính OutputCache mà không có VaryByCustom = "User", thì rõ ràng là trang có thể trả về bất kỳ phiên bản được lưu trong bộ nhớ cache bất kể yêu cầu người dùng nào đã tạo bản sao được lưu trong bộ nhớ cache. Làm thế nào nguy hiểm này?

+0

olive - ngoài câu trả lời của tôi bên dưới, bạn có thể tìm kiếm bài đăng gốc của TheCloudlessSky, nơi tôi có ý tưởng từ trong mã của tôi ... cũng tung ra mọi thứ không cần thiết về việc tiêm dịch vụ, hoặc Phiên ... tất cả cụ thể với tôi. Điều quan trọng là xử lý xác thực bộ nhớ cache trong hàm OnAuthorization() theo cách bạn cần để làm việc. :) Bảo trọng. –

+0

Điều gì xảy ra nếu bạn sử dụng "UseSlidingExpiration = False" để thực thi hết hạn tuyệt đối? – lalibi

+1

Bạn có biết nếu vấn đề này vẫn còn tồn tại? Tôi dường như có nó với MVC5 và nó có vẻ không phổ biến khác với bài đăng này. Có vẻ thực sự kỳ lạ rằng nó không hoạt động. Tôi không thể tưởng tượng bằng cách sử dụng bộ nhớ đệm và bộ nhớ đệm đầu ra azure là không phổ biến – GraemeMiller

Trả lời

7

Caching xảy ra trước Hành động. Bạn có thể sẽ cần phải tùy chỉnh cơ chế ủy quyền của bạn để xử lý các tình huống bộ nhớ cache.

Kiểm tra câu hỏi mà tôi đã đăng một lúc trở lại - MVC Custom Authentication, Authorization, and Roles Implementation.

Phần tôi nghĩ sẽ giúp bạn là Thuộc tính ủy quyền tùy chỉnh của ai là giao dịch phương pháp OnAuthorize() với bộ nhớ đệm.

Dưới đây là một khối mã ví dụ:

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

Đó là những gì tôi nghĩ. AuthorizeCache (ngữ cảnh) đến từ đâu? Nó là một phương pháp tĩnh? Điều này hiện đang cho tôi một lỗi trình biên dịch "Không thể giải quyết biểu tượng" AuthorizeCache '" – danludwig

+0

kiểm tra lại câu trả lời - Tôi đã thêm phương thức cache. –

+0

Xem Tôi nghĩ rằng đây là vấn đề với Azure. Tôi nghĩ rằng oob MVC3 AuthorizeAttribute có một phương thức gọi lại xác thực không tĩnh. Ngoại lệ trong câu hỏi nói rằng "Khi sử dụng nhà cung cấp bộ đệm đầu ra tùy chỉnh như 'DistributedCache', chỉ có các chính sách hết hạn và tính năng bộ nhớ cache sau được hỗ trợ: ... gọi lại xác thực tĩnh ...". Phương thức của bạn sử dụng một biến mẫu, vì vậy nó không thể được đánh dấu là tĩnh. Tôi có thể thêm một cuộc gọi xác thực tĩnh, nhưng nó nên làm gì? Khi nào nó trả về Valid, Invalid, và IgnoreThisRequest, dựa trên HttpContext arg chỉ? – danludwig

2

Bạn đúng là ô liu. Caching hoạt động bằng cách lưu trữ toàn bộ đầu ra của Action (bao gồm tất cả các thuộc tính), sau đó trả về kết quả cho các cuộc gọi tiếp theo mà không thực sự gọi bất kỳ mã nào của bạn.

Vì lý do này, bạn không thể lưu và kiểm tra ủy quyền vì bộ nhớ đệm bạn sẽ không gọi bất kỳ mã nào của bạn (bao gồm cả ủy quyền). Do đó, mọi thứ được lưu trong bộ nhớ cache phải được công khai.

+1

Tôi đã cập nhật câu hỏi của mình. Tôi có thể lưu trữ thành công nội dung phụ thuộc vào người dùng trong IIS Express, sử dụng bộ nhớ đệm tích hợp sẵn. Vấn đề là làm điều này trong Azure. – danludwig

+0

Tôi không biết rằng bạn có thể thay đổi bởi người dùng, nhưng bạn là chính xác rằng nó hoạt động như bạn đã nói với IIS cục bộ. Azure sử dụng bộ nhớ đệm AppFabric so với bộ nhớ đệm cục bộ, nhưng vì mã bộ nhớ đệm thực thi trên máy chủ ứng dụng, tôi không chắc chắn tại sao nó không hoạt động. Bạn đã ghi đè lên cùng một phương pháp trên DistributedCacheProvider? – Evan

+1

Ghi đè VaryByCustom nằm trong global.asax. DistributedOutputCacheProvider nằm trong tệp Microsoft.Web.DistributedCache. Tôi nghĩ rằng người ta có thể ghi đè lên AuthorizeAttribute và gọi 'filterContext.HttpContext.Cache.AddValidationCallback'. Tuy nhiên đối với bộ nhớ cache Azure, trình xử lý arg phải là một phương thức tĩnh. Tôi không phải là chuyên gia bảo mật hay chuyên gia về bộ nhớ cache, vì vậy tôi tự hỏi liệu có ai đó đã có mã cho việc này không. – danludwig

6

Tôi đã quay trở lại vấn đề này và sau một chút mày mò, đã kết luận rằng bạn không thể sử dụng ra khỏi hộp System.Web.Mvc.AuthorizeAttribute cùng bằng cách ra khỏi hộp System.Web.Mvc.OutputCacheAttributekhi sử dụng Azure DistributedCache. Lý do chính là bởi vì, như thông báo lỗi trong trạng thái câu hỏi ban đầu, phương thức gọi lại xác thực phải là tĩnh để sử dụng nó với DistributedCache của Azure. Phương thức gọi lại bộ nhớ cache trong thuộc tính ủy quyền MVC là một phương thức thể hiện.

Tôi đã cố gắng tìm cách làm cho nó hoạt động bằng cách tạo một bản sao của AuthorizeAttribute từ nguồn MVC, đổi tên nó, hooking nó lên đến một hành động với OutputCache kết nối với Azure, và gỡ lỗi. Lý do phương thức gọi lại bộ nhớ cache không tĩnh là vì, để cho phép, thuộc tính cần kiểm tra Người dùng của HttpContext dựa vào các giá trị thuộc tính Người dùng và Vai trò được thiết lập khi thuộc tính được xây dựng. Đây là mã có liên quan:

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

cache Validation Callback

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

Như bạn có thể thấy, gọi lại xác nhận bộ nhớ cache rốt cuộc gọi AuthorizeCore, đó là một phương pháp dụ (bảo vệ ảo). AuthorizeCore, mà cũng được gọi là trong OnAuthorization, làm 3 điều chính:

  1. Kiểm tra rằng HttpContextBase.User.Identity.IsAuthenticated == true

  2. Nếu thuộc tính có một tài sản không trống Users chuỗi , kiểm tra xem HttpContextBase.User.Identity.Name có khớp với một trong các giá trị được phân tách bằng dấu phẩy hay không.

  3. Nếu thuộc tính có thuộc tính chuỗi Roles không trống, hãy kiểm tra HttpContextBase.User.IsInRole cho một trong các giá trị được phân tách bằng dấu phẩy.

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

Khi bạn chỉ đơn giản là cố gắng làm cho phương pháp xác nhận callback tĩnh, mã sẽ không biên dịch bởi vì nó cần truy cập vào các lĩnh vực này _rolesSplit và _usersSplit, mà là dựa trên các thuộc tính Người dùng và Vai trò công khai.

Lần thử đầu tiên của tôi là chuyển các giá trị này cho cuộc gọi lại bằng cách sử dụng đối số object data của số CacheValidateHandler. Ngay cả sau khi giới thiệu các phương pháp tĩnh, điều này vẫn không hoạt động, và dẫn đến cùng một ngoại lệ. Tôi đã hy vọng rằng dữ liệu đối tượng sẽ được tuần tự hóa, sau đó truyền lại cho trình xử lý hợp lệ trong khi gọi lại. Rõ ràng đây không phải là trường hợp, và khi bạn cố gắng để làm điều này, DistributedCache của Azure vẫn coi nó là một gọi lại không tĩnh, dẫn đến cùng một ngoại lệ & tin nhắn.

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

nỗ lực thứ hai của tôi là để thêm giá trị cho bộ sưu tập HttpContext.Items, kể từ khi một thể hiện của HttpContext được tự động truyền cho handler. Điều này cũng không hiệu quả. Các HttpContext được chuyển đến CacheValidateHandlerkhông giống với trường hợp đã tồn tại trên thuộc tính filterContext.HttpContext. Trong thực tế, khi CacheValidateHandler thực hiện, nó có một Session rỗng và luôn có một bộ sưu tập Items rỗng.

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

Tuy nhiên ...

Mặc dù có vẻ là không có cách nào để vượt qua giá trị tài sản Users & Vai trò trở lại xử lý gọi lại xác nhận bộ nhớ cache, các HttpContext truyền cho nó nào trong thực tế có đúng Người dùng chính. Ngoài ra, không có hành động nào mà tôi hiện đang muốn kết hợp [Authorize] và [OutputCache] bao giờ chuyển thuộc tính Users hoặc Roles tới hàm tạo AuthorizeAttribute.

Vì vậy, bạn có thể tạo AuthenticateAttribute tùy chỉnh bỏ qua các thuộc tính này và chỉ kiểm tra để đảm bảo rằng User.Identity.IsAuthenticated == true. Nếu bạn cần xác thực đối với một vai trò cụ thể, bạn cũng có thể làm như vậy và kết hợp với OutputCache ... tuy nhiên, bạn sẽ cần một thuộc tính riêng biệt cho mỗi (bộ) Role (vai trò) để làm cho phương thức gọi lại xác thực bộ nhớ cache tĩnh . Tôi sẽ quay lại và đăng mã sau khi tôi đã đánh bóng nó một chút.