ASP. Net Core 2.2 с аутентификацией cookie: как избежать перенаправления страницы, если она не авторизована только для контроллеров API

#c# #asp.net-mvc #asp.net-core

#c# #asp.net-mvc #asp.net-core

Вопрос:

Мой ASP.В веб-приложении Net Core 2.2 есть некоторые контроллеры, которые возвращают View (ы) и некоторые контроллеры, которые возвращают результат json, поскольку они являются просто контроллерами API.

Мой веб-сайт использует аутентификацию cookie с помощью этого объявления:

 services
    .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(options =>
    {
        options.AccessDeniedPath = "/Pages/AccessDenied";
        options.Cookie.HttpOnly = true;
        options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
        options.Cookie.SameSite = SameSiteMode.Strict;
    });
 

В Configuration разделе:

 // ...
app.UseAuthentication();
app.UseDefaultFiles();
app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "default",
        template: "{controller=Pages}/{action=Index}/{id?}");
});
 

Мой PagesController просто наследуется от Microsoft.AspNetCore.Mvc.Controller и для некоторых страниц я помещаю [Authorize] атрибут. Когда неавторизованный пользователь пытается открыть страницу, он корректно перенаправляется на мою общедоступную AccessDenied страницу. Все хорошо.

НО у меня также есть an ApiController , который наследуется от Microsoft.AspNetCore.Mvc.ControllerBase , имеет атрибут [ApiController] , а также a [Authorize(Roles = "Administrator")] . Я использую этот контроллер для связи с javascript на своих страницах. Проблема в том, что когда неавторизованный пользователь пытается вызвать методы на этом контроллере, я не хочу, чтобы он отвечал страницей HTTP 200 (той AccessDenied самой), но я хочу, чтобы он возвращал только HTTP 401, неавторизованный, потому что это API.

Как я могу добиться такого другого поведения, сохраняя аутентификацию cookie?

Спасибо!

Комментарии:

1. по сути, вы хотите, чтобы, когда несанкционированный вызов AJAX достигал одного из ваших контроллеров API, HTTP-ответ представлял собой необработанный код состояния 401, верно? (вместо перенаправления 302 на страницу входа или страницу отказа в доступе)

2. Используете ли вы контроллеры API только для вызовов AJAX, поступающих с ваших интерфейсных страниц?

3. Да на оба вопроса!

Ответ №1:

Самый простой способ добиться такого поведения — написать пользовательский класс, который является производным от CookieAuthenticationEvents и переопределяет как RedirectToLogin, так и RedirectToAccessDenied .

Если вы не знакомы с CookieAuthenticationEvents классом, вы можете начать с этого примера в документации. По сути, это точка подключения для добавления настраиваемого поведения для аутентификации cookie.

Прежде всего, вам нужен способ идентифицировать ваши запросы AJAX. Самый простой способ сделать это — использовать общий префикс в пути запроса для всех ваших запросов AJAX. В качестве примера вы можете решить, что все ваши пути запросов AJAX будут начинаться с /api .

Вы можете написать следующий пользовательский CookieAuthenticationEvents класс:

 public sealed class CustomCookieAuthenticationEvents : CookieAuthenticationEvents 
{
  public override Task RedirectToAccessDenied(
            RedirectContext<CookieAuthenticationOptions> context)
  {
    if (context is null)
    {
      throw new ArgumentNullException(nameof(context));
    }

    if (IsAjaxRequest(context.HttpContext))
    {
      context.Response.StatusCode = StatusCodes.Status403Forbidden;
      return Task.CompletedTask;
    }
    else 
    {
      // non AJAX requests behave as usual
      return base.RedirectToAccessDenied(context);
    }
  }

  public override Task RedirectToLogin(RedirectContext<CookieAuthenticationOptions> context)
  {
    if (context is null)
    {
      throw new ArgumentNullException(nameof(context));
    }

    if (IsAjaxRequest(context.HttpContext))
    {
      context.Response.StatusCode = StatusCodes.Status401Unauthorized;
      return Task.CompletedTask;
    }
    else 
    {
      // non AJAX requests behave as usual
      return base.RedirectToLogin(context);
    }
  }

  private static bool IsAjaxRequest(HttpContext httpContext) 
  {
    var requestPath = httpContext.Request.Path;
        return requestPath.StartsWithSegments(new PathString("/api"), StringComparison.OrdinalIgnoreCase);
  }
}
 

Теперь вы можете зарегистрировать свой пользовательский CookieAuthenticationEvents интерфейс в службах аутентификации внутри вашего Startup.ConfigureServices метода:

 services
    .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(options =>
    {
        options.AccessDeniedPath = "/Pages/AccessDenied";
        options.Cookie.HttpOnly = true;
        options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
        options.Cookie.SameSite = SameSiteMode.Strict;
        options.EventsType = typeof(CustomCookieAuthenticationEvents);
    });

services.AddSingleton<CustomCookieAuthenticationEvents>();
 

Комментарии:

1. Вы также можете просто подписаться на события, используя предоставленное свойство Events следующим образом: options.Events.RedirectToLogin = context => { context.Response.StatusCode = 401; return Task.CompletedTask; }