1

В ASP.NET 4 MVC5 у меня был этот класс, который позволял мне возвращать настраиваемые ответы для не прошедших проверку ответов на конечные точки JSON. Вот.Пользовательское разрешение IActionРезультаты в aspnet-5 mvc-6

public class CustomAuthorizeAttribute : AuthorizeAttribute 
{ 
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) 
    { 
     if (IsAjax(filterContext)) 
     { 
      filterContext.Result = new JsonResult 
      { 
       JsonRequestBehavior = JsonRequestBehavior.AllowGet, 
       Data = new 
       { 
        success = false, 
        error = "You must be signed in." 
       } 
      }; 
     } 
     else 
     { 
      base.HandleUnauthorizedRequest(filterContext); 
     } 
    } 

    private bool IsAjax(AuthorizationContext filterContext) 
    { 
     return filterContext.ActionDescriptor.GetFilterAttributes(true).OfType<AjaxAttribute>().FirstOrDefault() != 
       null; 
    } 
} 

Однако в MVC6, новый AuthorizeAttribute нет переопределения для создания пользовательских IActionResult результатов. Как это сделать в MVC6?

ответ

0

Я, наконец, понял это, посмотрев на источник.

public class CustomCookieAuthenticationEvents : CookieAuthenticationEvents 
{ 
    Func<CookieRedirectContext, Task> _old; 
    public CustomCookieAuthenticationEvents() 
    { 
     _old = OnRedirectToLogin; 
     OnRedirectToLogin = OnCustomRedirectToLogin; 
    } 

    public Task OnCustomRedirectToLogin(CookieRedirectContext context) 
    { 
     var actionContext = context.HttpContext.RequestServices.GetRequiredService<IActionContextAccessor>(); 
     if (actionContext.ActionContext == null) 
      return _old(context); 

     if (actionContext.ActionContext.ActionDescriptor.FilterDescriptors.Any(x => x.Filter is AjaxAttribute)) 
     { 
      // this is an ajax request, return custom JSON telling user that they must be authenticated. 
      var serializerSettings = context 
       .HttpContext 
       .RequestServices 
       .GetRequiredService<IOptions<MvcJsonOptions>>() 
       .Value 
       .SerializerSettings; 

      context.Response.ContentType = "application/json"; 

      using (var writer = new HttpResponseStreamWriter(context.Response.Body, Encoding.UTF8)) 
      { 
       using (var jsonWriter = new JsonTextWriter(writer)) 
       { 
        jsonWriter.CloseOutput = false; 
        var jsonSerializer = JsonSerializer.Create(serializerSettings); 
        jsonSerializer.Serialize(jsonWriter, new 
        { 
         success = false, 
         error = "You must be signed in." 
        }); 
       } 
      } 

      return Task.FromResult(0); 
     } 
     else 
     { 
      // this is a normal request to an endpoint that is secured. 
      // do what ASP.NET used to do. 
      return _old(context); 
     } 
    } 
} 

Затем использовать этот класс событий следующим образом:

services.Configure<IdentityOptions>(options => 
{ 
    options.Cookies.ApplicationCookie.Events = new CustomCookieAuthenticationEvents(); 
}); 

ASP.NET 5 уверены, что сделали простые вещи труднее сделать. Конечно, теперь я могу настраивать вещи на более гранулированном уровне, не производя других. Кроме того, исходный код удивительно легко читать/понимать. Я очень доволен уверенностью в том, что любой вопрос, который у меня есть, можно легко определить как ошибку или решить, посмотрев на источник.

Приветствия будущему!

+1

из интереса, что случилось с пропускающими 401 или 403 сделать его обратно на сторону клиента и реагировать соответствующим образом? Это та сцена, к которой мы стремимся (и не используем cookie auth для ajax, из-за проблем CSRF). Вот почему трудно делать то, что вы хотите, это не то, что мы ожидаем от людей. – blowdart

+0

Поскольку в моем javascript обратные вызовы с ошибкой jquery являются терминальными ошибками, которые останавливают рабочий процесс, а 200 запросов с ошибкой = true являются ожидаемыми ошибками, которые являются частью рабочего процесса. Я не ожидаю ошибки. Это упрощает мой код на стороне клиента. Это не опубликованный/публичный API, поэтому я в порядке, не придерживаясь конвенций. –

+0

Кроме того, когда речь идет о закрытых приложениях с неопубликованными API-интерфейсами, вы не должны использовать соглашения для соглашений. Угадайте, что делает? ;) http://i.imgur.com/qqTFGWP.png –

1

Хороший момент был сделан @blowdart в his comment о том, следует ли ожидать возвращения 401/403. В любом случае, я пробовал использовать другой подход для выполнения задач, заданных OP, изменяя поведение фильтров авторизации MVC по умолчанию, чтобы мы возвращали json, когда пользователь несанкционирован.

Первое, что я сделал, это создать новый IAsyncAuthorizationFilter, который отформатирует несанкционированный результат как json для запроса ajax. Это будет в основном:

  1. Wrap существующий фильтр
  2. Выполнить обернутый фильтр
  3. В случае, если пользователь не авторизован на обернутой фильтром, возвращает JSON для AJAX-запросы

Это будет CustomJsonAuthorizationFilter класс:

public class CustomJsonAuthorizationFilter : IAsyncAuthorizationFilter 
{ 
    private AuthorizeFilter wrappedFilter; 
    public CustomJsonAuthorizationFilter(AuthorizeFilter wrappedFilter) 
    { 
     this.wrappedFilter = wrappedFilter; 
    } 

    public async Task OnAuthorizationAsync(Microsoft.AspNet.Mvc.Filters.AuthorizationContext context) 
    { 
     await this.wrappedFilter.OnAuthorizationAsync(context); 
     if(context.Result != null && IsAjaxRequest(context)) 
     { 
      context.Result = new JsonResult(new 
      { 
       success = false, 
       error = "You must be signed in." 
      }); 
     } 
     return; 
    } 

    //This could be an extension method of the HttpContext/HttpRequest 
    private bool IsAjaxRequest(Microsoft.AspNet.Mvc.Filters.AuthorizationContext filterContext) 
    { 
     return filterContext.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest"; 
    } 
} 

Затем я создал IApplicationModelProvider, чтобы обернуть все существующие AuthorizeFilter новым пользовательским фильтром. AuthroizeFilter добавляется AuthorizationApplicationModelProvider, но новый провайдер будет запущен по умолчанию, так как порядок поставщика по умолчанию -990.

public class CustomFilterApplicationModelProvider : IApplicationModelProvider 
{ 
    public int Order 
    { 
     get { return 0; } 
    } 

    public void OnProvidersExecuted(ApplicationModelProviderContext context) 
    { 
     //Do nothing 
    } 

    public void OnProvidersExecuting(ApplicationModelProviderContext context) 
    { 
     this.ReplaceFilters(context.Result.Filters); 
     foreach(var controller in context.Result.Controllers) 
     { 
      this.ReplaceFilters(controller.Filters); 
      foreach (var action in controller.Actions) 
      { 
       this.ReplaceFilters(action.Filters); 
      } 
     } 
    } 

    private void ReplaceFilters(IList<IFilterMetadata> filters) 
    { 
     var authorizationFilters = filters.OfType<AuthorizeFilter>().ToList(); 
     foreach (var filter in authorizationFilters) 
     { 
      filters.Remove(filter); 
      filters.Add(new CustomJsonAuthorizationFilter(filter)); 
     } 
    } 
} 

Наконец, обновление ConfigureServices в запуске с новым поставщиком модели приложения:

services.TryAddEnumerable(
      ServiceDescriptor.Transient<IApplicationModelProvider, CustomFilterApplicationModelProvider>()); 
+0

Более одного способа кошки кошки! Мне нравится этот подход, потому что он остается в мире MVC. Мое решение было в промежуточном программном обеспечении с * peek * ('' 'IActionContextAccessor''') в мире MVC. –

+0

Кроме того, что касается @blowdart, когда речь идет о закрытых приложениях с неопубликованными API-интерфейсами, вы не должны использовать соглашения для соглашений. Угадайте, что делает? ;) http://i.imgur.com/qqTFGWP.png –

+0

хороший улов с SO :) –

Смежные вопросы