PAYATHON 2026

为什么 [Authorize] 仍跳转到 /Account/Login?

数据阿明

结论

[Authorize] 不决定登录页面地址。你的 Action 指定了 Identity.Application,未登录时,challenge 会交给该认证方案处理。使用 Cookie 认证时,登录跳转地址由对应的 CookieAuthenticationOptions 和事件处理程序决定。

修改后的 OnRedirectToLogin 只设置了 401,没有执行重定向。如果这段配置已应用到当前请求使用的 Cookie 处理程序,执行该事件时,处理程序就不会生成登录跳转。既然请求仍然跳到了 /Account/Login,需要核实实际运行的应用、最终生效的配置,以及跳转是否来自其他位置。仅凭现有代码,还无法确定具体原因。

原始配置中有重复注册

AddIdentity<ApplicationUser, IdentityRole>() 已经注册了 Identity 使用的 Cookie 认证方案,其中 IdentityConstants.ApplicationScheme 的名称就是 Identity.Application。

后面再次调用:

.AddCookie(IdentityConstants.ApplicationScheme, options =>
{
    options.LoginPath = "/Admin/Login";
});

会重复注册同名方案。在标准的 Identity 注册流程下,这通常会在认证方案初始化时触发 Scheme already exists: Identity.Application 异常,不能用来修改现有 Cookie 配置。

应删除这次 AddCookie,通过 ConfigureApplicationCookie 配置 Identity 已注册的应用 Cookie。如果运行原始配置时没有出现重复方案异常,也可以检查实际执行的启动代码是否与贴出的代码一致。

为 MVC 页面和 API 分别指定认证方式

如果 MVC 页面使用 Identity Cookie 登录,API 使用 JWT,可以保留 Identity 注册,再添加 JWT 方案。下面的服务注册示例沿用问题中的 JWT 配置项:

builder.Services.AddIdentity<ApplicationUser, IdentityRole>()
    .AddEntityFrameworkStores<StoreIdentityDbContext>()
    .AddDefaultTokenProviders();

builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
    options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
    options.SaveToken = true;
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidIssuer = builder.Configuration["JWTOptions:Issuer"],
        ValidAudience = builder.Configuration["JWTOptions:Audience"],
        IssuerSigningKey = new SymmetricSecurityKey(
            Encoding.UTF8.GetBytes(
                builder.Configuration["JWTOptions:SecretKey"]!))
    };
});

builder.Services.ConfigureApplicationCookie(options =>
{
    options.LoginPath = "/Admin/Login";
    options.AccessDeniedPath = "/Admin/AccessDenied";
    options.Cookie.Name = "GymunityAuthCookie";
});

builder.Services.AddScoped<IAuthenticationServices, AuthenticationServices>();

相关命名空间:

using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.IdentityModel.Tokens;

MVC Action 使用 Identity Cookie:

[Authorize(AuthenticationSchemes = IdentityConstants.ApplicationScheme)]
public async Task<IActionResult> Index()
{
    // 保留现有商品查询和视图返回逻辑。
}

需要 JWT 的 API 则明确指定 Bearer:

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[HttpGet("api/products")]
public IActionResult GetProducts()
{
    return Ok();
}

注册 JWT 后,并非所有 [Authorize] 都会自动使用 JWT。当前页面已显式指定 Identity.Application,即使请求携带 Bearer Token,也不能依靠该 Token 通过页面认证。

如果 UI 和 API 是独立启动的项目,它们各自拥有独立的服务容器和认证配置。相关注册必须放在实际处理请求的宿主中;只修改另一个项目的 Program 不会改变 UI 的行为。

登录跳转和直接返回 401 应按需求选择

如果 MVC 页面需要跳转到 /Admin/Login,保留 LoginPath 配置,删除新增的两个事件覆盖,让 Cookie 处理程序使用默认重定向事件。登录入口还应允许匿名访问,避免再次触发授权:

[AllowAnonymous]
public IActionResult Login()
{
    return View();
}

如果希望使用该 Cookie 方案的请求直接返回状态码,可以保留:

builder.Services.ConfigureApplicationCookie(options =>
{
    options.Events.OnRedirectToLogin = context =>
    {
        context.Response.StatusCode = StatusCodes.Status401Unauthorized;
        return Task.CompletedTask;
    };

    options.Events.OnRedirectToAccessDenied = context =>
    {
        context.Response.StatusCode = StatusCodes.Status403Forbidden;
        return Task.CompletedTask;
    };
});

这两个处理程序作用于应用 Cookie 方案,并非只作用于 JWT API。上述 OnRedirectToLogin 已替换默认的重定向行为,因此执行时即使设置了 LoginPath,也不会自动跳转。

明确使用 JWT Bearer 的 API 未通过认证时,通常由 Bearer 处理程序返回 401,不需要为此修改 Identity Cookie 事件。

按实际响应定位 /Account/Login 的来源

1. 查看第一次响应

在浏览器开发者工具的 Network 面板中保留请求记录,未登录访问 /products/index,查看最初响应的状态码和 Location:

  • 如果返回 302,且 Location 指向 /Account/Login,说明某个服务端环节生成了重定向。
  • 如果最初返回 401,随后浏览器又请求 /Account/Login,应检查前端的统一错误处理或登录跳转逻辑。
  • 如果先跳到 /Admin/Login,再跳到 /Account/Login,需要检查登录页面自身及其授权配置。

浏览器最终停留的地址不足以说明第一次跳转由谁产生。

在修改后的 OnRedirectToLogin 中设置断点,再访问受保护页面。

如果断点没有命中,检查请求是否到达预期的 UI 项目、当前进程是否已重新启动,以及是否存在后续配置覆盖事件。也要检查是否设置了 options.EventsType;如果设置了它,运行时可能使用从依赖注入容器解析的事件对象。

如果断点命中,且该响应返回了 401,继续检查后续中间件、代理或前端脚本是否将其转换成登录跳转。

3. 检查最终生效的具名配置

可以在调试器中检查当前宿主解析出的应用 Cookie 配置:

var cookieOptions = app.Services
    .GetRequiredService<IOptionsMonitor<CookieAuthenticationOptions>>()
    .Get(IdentityConstants.ApplicationScheme);

需要的命名空间:

using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.Extensions.Options;

查看 cookieOptions.LoginPath、cookieOptions.EventsType 和事件配置,并搜索项目中的 ConfigureApplicationCookie、针对 CookieAuthenticationOptions 的 Configure/PostConfigure、OnRedirectToLogin,以及硬编码的 /Account/Login。

修改 Cookie 名称有助于识别 Cookie,但不能决定登录跳转地址。

4. 核对请求管道

使用显式中间件配置时,认证和授权应位于路由匹配之后、端点执行之前,例如:

app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();

app.MapDefaultControllerRoute();

同时确认 /Admin/Login 路由实际存在。如果 Admin 是 Area,需要配置相应的 Area 路由;只设置 LoginPath 不会自动创建登录端点。

备注:内容仅供参考。