JWT授权失败排查:Identity Server访问[Authorize]端点返回Unauthorized
我正在开发一个入门级Identity Server,目标是让服务生成JWT令牌,用于用户认证并配置授权策略。目前已成功生成令牌,但访问带有[Authorize]特性的端点时,持续收到Unauthorized响应。
免责声明:
以下的密钥和令牌均为模拟生成。请勿分享(更不要发布到网络论坛)任何密钥、JWT或敏感数据。我这么做是因为该应用不会投入生产,仅用于学习目的。
Program.cs
... // Authorization builder.Services .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.RequireHttpsMetadata = false; options.TokenValidationParameters = new TokenValidationParameters { IssuerSigningKey = new SymmetricSecurityKey( Encoding.ASCII.GetBytes("26837a75-3199-4e7d-8c31-2c3f53f42e83")), ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = false, ValidateIssuerSigningKey = false, ValidateActor = false, ValidateSignatureLast = false, ValidateTokenReplay = false, ValidateWithLKG = false }; }); // HTTP request pipeline var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthorization(); app.UseAuthentication(); app.MapControllers(); app.MapHealthChecks("/healthz"); app.Run();
令牌生成工厂(省略完整控制器,该代码已能生成Bearer令牌)
public string CreateToken( MetaToken metaToken) { var tokenDuration = GetTokenDuration(metaToken); var expiresAt = DateTime.UtcNow.Add(tokenDuration); var tokenDescriptor = new SecurityTokenDescriptor { Expires = expiresAt, Subject = new ClaimsIdentity(new Claim[] { new Claim(JwtRegisteredClaimNames.Exp, expiresAt.Ticks.ToString()), new Claim(JwtRegisteredClaimNames.Nbf, DateTimeOffset.UtcNow.ToString()), new Claim(JwtRegisteredClaimNames.Iat, metaToken.CreatedAt.Ticks.ToString()), new Claim(JwtRegisteredClaimNames.Jti, metaToken.Id.ToString()) }) }; return CreateTokenInternal(tokenDescriptor); } private string CreateTokenInternal(SecurityTokenDescriptor tokenDescriptor) { // Setup signing credentials var secretBytes = Encoding.ASCII.GetBytes("26837a75-3199-4e7d-8c31-2c3f53f42e83"); var key = new SymmetricSecurityKey(secretBytes); var algorithm = SecurityAlgorithms.HmacSha256Signature; tokenDescriptor.SigningCredentials = new SigningCredentials(key, algorithm); // Create the token var tokenHandler = new JwtSecurityTokenHandler(); var token = tokenHandler.CreateToken(tokenDescriptor); return tokenHandler.WriteToken(token); }
问题排查与修复
1. 中间件顺序错误
HTTP请求管道中,认证逻辑必须在授权逻辑之前执行。当前代码里UseAuthorization()先于UseAuthentication()运行,导致授权校验在用户身份完成认证前就触发,直接返回Unauthorized。
修复后的管道代码:
app.UseHttpsRedirection(); app.UseAuthentication(); // 先完成用户身份认证 app.UseAuthorization(); // 再执行授权策略校验 app.MapControllers(); app.MapHealthChecks("/healthz"); app.Run();
2. JWT标准声明格式错误
exp、nbf、iat这些JWT标准声明要求使用Unix时间戳(秒),但当前代码用了Ticks或DateTimeOffset字符串,即使关闭了ValidateLifetime,错误的声明格式仍会让认证逻辑无法识别令牌合法性。
修复后的令牌生成代码:
public string CreateToken(MetaToken metaToken) { var tokenDuration = GetTokenDuration(metaToken); var expiresAt = DateTime.UtcNow.Add(tokenDuration); // 转换为符合要求的Unix时间戳(秒) var unixExpiresAt = new DateTimeOffset(expiresAt).ToUnixTimeSeconds(); var unixCurrentTime = new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds(); var tokenDescriptor = new SecurityTokenDescriptor { Expires = expiresAt, Subject = new ClaimsIdentity(new Claim[] { new Claim(JwtRegisteredClaimNames.Exp, unixExpiresAt.ToString()), new Claim(JwtRegisteredClaimNames.Nbf, unixCurrentTime.ToString()), new Claim(JwtRegisteredClaimNames.Iat, unixCurrentTime.ToString()), new Claim(JwtRegisteredClaimNames.Jti, metaToken.Id.ToString()) }) }; return CreateTokenInternal(tokenDescriptor); }
3. 关闭签名验证存在风险(建议开启)
当前配置中ValidateIssuerSigningKey = false,会跳过令牌签名的合法性校验,即使是学习环境,也建议开启该选项,确保令牌确实由你的服务生成:
修复后的TokenValidationParameters:
options.TokenValidationParameters = new TokenValidationParameters { IssuerSigningKey = new SymmetricSecurityKey( Encoding.ASCII.GetBytes("26837a75-3199-4e7d-8c31-2c3f53f42e83")), ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = false, // 学习阶段可暂时关闭,后续建议开启 ValidateIssuerSigningKey = true, // 开启签名验证 ValidateActor = false, ValidateSignatureLast = false, ValidateTokenReplay = false, ValidateWithLKG = false };
额外验证步骤
- 生成令牌后,解析令牌检查声明格式是否正确、签名是否有效。
- 确保请求时在
Authorization请求头中正确携带Bearer {你的令牌}格式的内容。
内容的提问来源于stack exchange,提问作者sebsmgzz
相关产品推荐
相关产品推荐

