首页 > 解决方案 > ASP。带有 cookie 身份验证的 Net Core 2.2:当未授权仅 API 控制器时如何避免页面重定向

问题描述

我的 ASP .Net Core 2.2 Web 应用程序有一些返回View(s) 的控制器和一些返回 json 结果的控制器,因为它们只是API控制器。

我的网站使用cookie authentication,声明如下:

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页面。一切都很好。

但是,我也有一个ApiController,它继承自Microsoft.AspNetCore.Mvc.ControllerBase,有一个属性[ApiController],还有一个[Authorize(Roles = "Administrator")]. 我使用此控制器与页面中的 javascript 进行通信。问题是,当非授权用户尝试调用此控制器上的方法时,我不希望它响应 HTTP 200 页面(那个AccessDenied),但我希望它只返回 HTTP 401 Unauthorized 因为它是一个 API。

我怎样才能实现这种不同的行为来保持 cookie 身份验证?

谢谢!

标签: c#asp.net-mvcasp.net-core

解决方案


获得此行为的最简单方法是编写一个派生自CookieAuthenticationEvents并覆盖RedirectToLoginRedirectToAccessDenied的自定义类。

如果你不熟悉这个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>();

推荐阅读