Я вернулся к этой проблеме и, немного поработав, пришел к выводу, что нельзя использовать "из коробки" System.Web.Mvc.AuthorizeAttribute
вместе с "* из коробки" System.Web.Mvc.OutputCacheAttribute
при использовании Azure DistributedCache . Основная причина в том, что, как говорится в сообщении об ошибке в исходном вопросе, метод обратного вызова проверки должен быть статическим, чтобы использовать его с DistributedCache Azure. Метод обратного вызова кэша в атрибуте MVC Authorize является методом экземпляра.
Я пытался выяснить, как заставить его работать, создав копию AuthorizeAttribute из источника MVC, переименовав его, подключив его к действию с OutputCache, подключенным к Azure, и отладив. Причина, по которой метод обратного вызова кэша не является статическим, заключается в том, что для авторизации атрибут должен проверить пользователя HttpContext по значениям свойств Users и Roles, которые устанавливаются при создании атрибута. Вот соответствующий код:
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, который является другим методом экземпляра (защищенный виртуальный). AuthorizeCore, который также вызывался во время OnAuthorization, выполняет 3 основных действия:
Проверяет, что HttpContextBase.User.Identity.IsAuthenticated == true
Если атрибут имеет непустое строковое свойство Users, проверяет соответствие HttpContextBase.User.Identity.Name одному из значений, разделенных запятыми.
Если атрибут имеет непустое строковое свойство Roles, проверяет, является ли HttpContextBase.User.IsInRole для одного из значений, разделенных запятыми.
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
. Даже после введения статических методов это все равно не сработало и привело к тому же исключению. Я надеялся, что данные объекта будут сериализованы, а затем переданы обратно обработчику проверки во время обратного вызова. По-видимому, это не тот случай, и при попытке сделать это DistributedCache Azure по-прежнему считает его нестатическим обратным вызовом, что приводит к тому же исключению и сообщению.
// this won't work
cachePolicy.AddValidationCallback(CacheValidateHandler, new object() /* data */);
Моя вторая попытка состояла в добавлении значений в коллекцию HttpContext.Items
, поскольку экземпляр HttpContext
автоматически передается в обработчик. Это тоже не сработало. HttpContext
, который передается в CacheValidateHandler
, не является тем же экземпляром , который существовал в свойстве filterContext.HttpContext
. Фактически, когда CacheValidateHandler выполняется, он имеет нулевой сеанс и всегда имеет пустую коллекцию элементов.
// 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], никогда не передает свойство Users или Roles в конструктор AuthorizeAttribute.
Таким образом, можно создать пользовательский атрибут AuthenticateAttribute, который игнорирует эти свойства и проверяет только User.Identity.IsAuthenticated == true
. Если вам нужно пройти аутентификацию для определенной роли, вы также можете сделать это и объединить с OutputCache ... однако вам потребуется отдельный атрибут для каждой (набора) ролей, чтобы сделать статический метод обратного вызова проверки кэша статическим. , Я вернусь и выложу код после того, как немного его отполирую.