ASP.NET Core 6+ 中 [Authorize] 失效主因是 AddJwtBearer() 未注册或顺序错误:必须在 AddControllers() 前注册,UseAuthentication() 在 UseRouting() 后、UseEndpoints() 前;需显式指定默认 Scheme、校验参数、密钥长度≥32 字节、ClockSkew=TimeSpan.Zero,且 header 必须为 Authorization: bearer 。

ASP.NET Core 6+ 中,[Authorize] 失效、接口直接返回 401,90% 是因为 AddJwtBearer() 没注册,或注册顺序错了——它必须在 AddControllers() 之前调用,且 UseAuthentication() 必须在 UseRouting() 之后、UseEndpoints() 之前。
Program.cs 中 JWT 认证服务怎么注册才生效
漏掉 AddAuthentication().AddJwtBearer(),整个认证链就断了,所有带 [Authorize] 的控制器/方法都会被中间件拦在门外,连控制器的 OnActionExecuting 都不会进。
-
AddAuthentication()必须明确指定默认 scheme:JwtBearerDefaults.AuthenticationScheme -
AddJwtBearer()要在AddControllers()或AddEndpointsApiExplorer()之前调用,否则依赖注入失败 -
TokenValidationParameters中ValidateIssuerSigningKey、ValidateIssuer、ValidateAudience默认为true,但若ValidIssuer或ValidAudience为空字符串或 null,token 会直接被拒(不是 401,是 500 或静默失败) - 密钥必须转成
SymmetricSecurityKey:不能直接传字符串,得用Encoding.UTF8.GetBytes("...")构造,且长度至少 32 字节(HS256 要求) - 建议显式设
ClockSkew = TimeSpan.Zero,避免服务器时间偏差导致 token 误判过期
生成 JWT Token 时最容易踩的三个坑
Token 生成是纯内存操作,不查库、不 IO,但错一个参数前端就拿不到有效 token。
-
Expires必须用DateTime.UtcNow+TimeSpan,写成DateTime.Now在非 UTC 时区服务器上会导致 token 提前失效 -
SigningCredentials的算法要和中间件里IssuerSigningKey匹配:如果中间件用的是HS256,生成时就不能用RS256,否则签名验不过 - 载荷里别塞密码、手机号、身份证号等敏感字段;只放标准声明(如
sub、roles)或业务必需 ID;自定义字段名尽量小写,避免大小写混淆引发 claim 解析失败
前端请求头格式错误导致 401 却查不出原因
后端一切配置正确,但前端发请求时 header 写错一个字符,就会卡在认证中间件,返回 401 且不进控制器——这是最隐蔽也最高频的问题。
- header key 必须是
Authorization(首字母大写,全称,不是authorization或X-Authorization) - value 格式必须是
bearer <token></token>(注意bearer全小写,后面一个空格,再跟 token 字符串;Bearer或JWT开头都不行) - token 字符串本身不能带多余空格、换行、引号;从后端返回时建议用
JsonSerializer.Serialize()直接输出,别手动拼接 - 如果用 Swagger 测试,确保它启用了
Authorize按钮并填入完整 token(不含bearer前缀)
为什么 User.Claims 里取不到自定义字段
生成 token 时写了 new Claim("role", "admin"),但控制器里 User.Claims.FirstOrDefault(x => x.Type == "role") 总是 null——问题不在 token 本身,而在中间件没把 claim 映射进来。
- 默认情况下,
AddJwtBearer()只映射标准声明(如sub、name、email),自定义字段会被忽略 - 必须在
TokenValidationParameters中设置NameClaimType = "name"、RoleClaimType = "role"(如果你用"role"当角色字段) - 或者更彻底:在
AddJwtBearer()的options.Events.OnTokenValidated里手动把自定义 claim 加进context.Principal.Identity - 验证方式:在任意受保护接口里加一行
Console.WriteLine(string.Join(", ", User.Claims.Select(c => $"{c.Type}={c.Value}"))),看实际解析出哪些字段
真正难的不是写几行配置,而是每个环节都得严丝合缝:密钥长度、时间基准、header 格式、claim 映射、中间件顺序——少一个,整个链路就断在你看不见的地方。


















