.NET 8中JWT生成正常但添加[Authorize]后返回404问题
.NET 8 JWT授权返回404问题解决方案
问题核心原因
- 密钥编码不一致:JWT验证时用
Encoding.ASCII解析密钥,生成Token时用Encoding.UTF8,导致签名验证失败,触发默认挑战行为(重定向到登录页),但API无此页面,返回404。 - 角色声明类型不匹配:验证配置中指定
RoleClaimType = "role",但生成Token时使用标准的ClaimTypes.Role(对应URI格式的声明类型),导致角色无法被识别,授权失败后触发404。 - 默认挑战行为未适配API:认证/授权失败时,框架默认重定向到登录页面,API场景下无对应路由,返回404而非标准401/403。
- TokenValidationParameters配置矛盾:
ValidateLifetime = true但RequireExpirationTime = false,导致验证逻辑混乱。
修复步骤
1. 统一密钥编码方式
确保生成Token和验证Token时使用相同的编码格式,统一用UTF8(适配含非ASCII字符的密钥)。
2. 对齐角色声明类型
将JWT验证配置中的RoleClaimType改为标准的ClaimTypes.Role,与生成Token时的声明类型保持一致。
3. 配置API友好的挑战行为
修改JwtBearer事件,在认证失败或需要挑战时直接返回401/403 JSON响应,避免重定向。
4. 修正TokenValidationParameters配置
将RequireExpirationTime设为true,与ValidateLifetime = true的配置匹配。
5. 优化中间件顺序
显式添加UseRouting(),确保路由逻辑在认证授权之前执行;调整自定义异常中间件到最外层,统一处理错误响应。
修复后的代码
Program.cs 关键修改
using System.Text.Json; // ... 其他using语句 ... builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(jwt => { // 统一用UTF8解析密钥,和生成Token时一致 var key = Encoding.UTF8.GetBytes(builder.Configuration.GetSection("JwtConfig:Secret").Value); jwt.SaveToken = true; jwt.TokenValidationParameters = new TokenValidationParameters() { ValidateLifetime = true, RequireExpirationTime = true, // 修正为true,匹配ValidateLifetime ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(key), ValidateIssuer = true, ValidIssuer = builder.Configuration.GetSection("JwtConfig:Issuer").Value, ValidateAudience = true, ValidAudience = builder.Configuration.GetSection("JwtConfig:Audience").Value, RoleClaimType = ClaimTypes.Role // 改为标准的角色声明类型 }; jwt.Events = new JwtBearerEvents { OnAuthenticationFailed = context => { Console.WriteLine($"Authentication failed: {context.Exception.Message}"); // 直接返回401,不重定向 context.Response.StatusCode = StatusCodes.Status401Unauthorized; context.Response.ContentType = "application/json"; return context.Response.WriteAsync(JsonSerializer.Serialize(new { Status = "Failed", Message = "Authentication failed: " + context.Exception.Message })); }, OnChallenge = context => { // 拦截默认挑战行为,返回401 JSON context.HandleResponse(); context.Response.StatusCode = StatusCodes.Status401Unauthorized; context.Response.ContentType = "application/json"; return context.Response.WriteAsync(JsonSerializer.Serialize(new { Status = "Failed", Message = "You are not authorized to access this resource" })); }, OnForbidden = context => { // 处理无权限场景,返回403 context.Response.StatusCode = StatusCodes.Status403Forbidden; context.Response.ContentType = "application/json"; return context.Response.WriteAsync(JsonSerializer.Serialize(new { Status = "Failed", Message = "You do not have permission to access this resource" })); } }; }); // ... 其他服务配置 ... var app = builder.Build(); // 中间件顺序调整 if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); app.UseHsts(); } app.UseHttpsRedirection(); // 自定义异常处理中间件(放在最外层) app.Use(async (context, next) => { try { await next.Invoke(); // 处理404情况,返回JSON响应 if (context.Response.StatusCode == StatusCodes.Status404NotFound) { context.Response.ContentType = "application/json"; await context.Response.WriteAsync(JsonSerializer.Serialize(new { Status = "Failed", Message = "Resource not found" })); } } catch (Exception e) { Console.WriteLine($"An error occurred: {e.Message}"); context.Response.StatusCode = StatusCodes.Status500InternalServerError; context.Response.ContentType = "application/json"; await context.Response.WriteAsync(JsonSerializer.Serialize(new { Status = "Failed", Message = "An unexpected error occurred: " + e.Message })); } }); // 显式添加路由中间件 app.UseRouting(); // 认证授权必须在路由之后,端点之前 app.UseAuthentication(); app.UseAuthorization(); // 映射端点 app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); // ... 角色和用户初始化代码 ...
AccountController 关键确认
确保生成Token时的密钥编码与验证一致(已为UTF8,无需修改):
private string GenerateJwtToken(User user) { try { var JwtTokenHandler = new JwtSecurityTokenHandler(); // 保持和验证时一致的UTF8编码 var key = Encoding.UTF8.GetBytes(_configuration.GetSection("JwtConfig:Secret").Value); // ... 其他代码 ... } catch (Exception ex) { _logger.LogError(ex, "Error generating JWT token"); throw; } }
AdminController 启用角色授权
[Route("api/[controller]")] [ApiController] [Authorize(Roles = "Overseer")] // 启用角色授权 public class AdminController(UserManager<User> userManager) : ControllerBase { // ... 现有代码 ... }
验证方法
- 登录获取Token:调用
POST /api/Account/Login,传入测试用户凭据(TimmyTurner/Password1!),获取JWT Token。 - 访问受保护路由:在请求头中添加
Authorization: Bearer <你的Token>,调用GET /api/Admin/Dashboard。- 若Token有效且用户有对应角色,返回
Welcome to the Dashboard。 - 若Token无效/过期,返回401 JSON响应。
- 若用户无对应角色,返回403 JSON响应。
- 若Token有效且用户有对应角色,返回
内容的提问来源于stack exchange,提问作者Timmy
相关产品推荐
相关产品推荐

