是否应使用[Authorize]特性?JWT会话存储下的授权疑问
问题根源
默认的JWT认证方案只会从Authorization请求头(Bearer格式)读取令牌,而浏览器直接输入地址发起的GET请求不会自动携带这个头,所以用[Authorize]会导致认证失败,跳转到登录页或者返回401。
正确解决方案:自定义认证方案,保留[Authorize]特性
你应该继续使用[Authorize]特性,但是需要扩展ASP.NET的认证体系,让它支持从Session中读取并验证JWT令牌。这样既符合框架的设计规范,又能利用其内置的授权功能(比如角色、声明校验),避免手动校验的重复工作。
步骤1:创建自定义认证处理程序
实现一个继承自AuthenticationHandler<AuthenticationSchemeOptions>的处理类,负责从Session读取JWT并完成验证:
public class SessionJwtAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions> { private readonly IHttpContextAccessor _httpContextAccessor; private readonly TokenValidationParameters _tokenValidationParams; public SessionJwtAuthenticationHandler( IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IHttpContextAccessor httpContextAccessor, IOptions<TokenValidationParameters> tokenValidationOptions) : base(options, logger, encoder, clock) { _httpContextAccessor = httpContextAccessor; _tokenValidationParams = tokenValidationOptions.Value; } protected override async Task<AuthenticateResult> HandleAuthenticateAsync() { var context = _httpContextAccessor.HttpContext; if (context == null) return AuthenticateResult.Fail("HttpContext unavailable"); // 从Session获取存储的JWT令牌 var jwtToken = context.Session.GetString("jwt-token"); if (string.IsNullOrEmpty(jwtToken)) return AuthenticateResult.NoResult(); // 无令牌,交由后续认证方案处理或返回未授权 try { // 验证JWT有效性 var tokenHandler = new JwtSecurityTokenHandler(); var principal = tokenHandler.ValidateToken(jwtToken, _tokenValidationParams, out var validatedToken); // 创建认证票据 var identity = new ClaimsIdentity(principal.Claims, Scheme.Name); var authTicket = new AuthenticationTicket(new ClaimsPrincipal(identity), Scheme.Name); return AuthenticateResult.Success(authTicket); } catch (Exception ex) { return AuthenticateResult.Fail($"Invalid JWT token: {ex.Message}"); } } }
步骤2:配置认证服务和Session
在Program.cs中注册Session服务、自定义认证方案,并配置JWT验证参数:
// 注册Session服务 builder.Services.AddSession(options => { options.IdleTimeout = TimeSpan.FromMinutes(30); options.Cookie.HttpOnly = true; options.Cookie.IsEssential = true; }); // 配置JWT验证参数 builder.Services.Configure<TokenValidationParameters>(options => { options.ValidateIssuer = true; options.ValidateAudience = true; options.ValidateLifetime = true; options.ValidateIssuerSigningKey = true; options.ValidIssuer = builder.Configuration["Jwt:Issuer"]; options.ValidAudience = builder.Configuration["Jwt:Audience"]; options.IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])); }); // 配置认证体系 builder.Services.AddAuthentication(options => { // 设置默认认证方案为自定义的SessionJwt方案 options.DefaultAuthenticateScheme = "SessionJwt"; options.DefaultChallengeScheme = "SessionJwt"; }) // 添加自定义SessionJwt认证方案 .AddScheme<AuthenticationSchemeOptions, SessionJwtAuthenticationHandler>("SessionJwt", _ => { }) // 可选:保留JWTBearer方案,支持API请求的Bearer令牌 .AddJwtBearer("JwtBearer", options => { options.TokenValidationParameters = builder.Configuration.GetSection("Jwt").Get<TokenValidationParameters>()!; }); builder.Services.AddAuthorization(); // 中间件顺序很重要:先Session,再认证,再授权 app.UseSession(); app.UseAuthentication(); app.UseAuthorization(); // 现在可以直接使用[Authorize]特性 app.MapGet("/home", [Authorize] () => { return Results.Content("<h1>Welcome to Home Page</h1>", "text/html"); });
为什么不推荐手动校验Session?
手动校验Session中的JWT虽然能实现功能,但存在以下问题:
- 重复造轮子:框架已经提供了成熟的认证授权体系,手动校验需要重复实现令牌验证、Claims解析等逻辑。
- 无法利用框架特性:比如
[Authorize(Roles = "Admin")]、[Authorize(Policy = "RequireEmailVerified")]这类声明/角色/策略授权,手动校验需要额外处理。 - 一致性问题:如果后续需要支持多种认证方式(比如API的Bearer令牌、第三方登录),手动校验会导致逻辑分散,难以维护。
扩展:同时支持Session和Bearer令牌
如果你的应用同时需要支持浏览器Session登录和API的Bearer令牌登录,可以在[Authorize]特性中指定多个认证方案:
app.MapGet("/home", [Authorize(AuthenticationSchemes = "SessionJwt,JwtBearer")] () => { return Results.Content("<h1>Welcome to Home Page</h1>", "text/html"); });
这样无论是从Session读取JWT,还是从Authorization头读取Bearer令牌,都能通过认证。
内容的提问来源于stack exchange,提问作者user24736035
相关产品推荐
相关产品推荐

