.NET 8 Web API使用JWT Bearer令牌后出现401未授权错误
.NET 8 Web API JWT认证401未授权问题排查
我正在为.NET 8.0.0版本的Web API实现认证与授权功能,采用JWT Bearer令牌方案。此前在.NET 7.0.0项目中使用相同方法可正常运行,但当前场景下:登录后能成功获取令牌,通过Swagger UI的授权按钮携带令牌访问带有[Authorize]特性的Get请求时,却返回401未授权错误。请帮忙排查原因,相关代码如下:
TokenService代码
public class TokenService : ITokenService { private const int ExpirationMinutes = 30; public string CreateToken(IdentityUser user) { var expiration = DateTime.UtcNow.AddMinutes(ExpirationMinutes); var token = CreateJwtToken( CreateClaims(user), CreateSigningCredentials(), expiration ); var tokenHandler = new JwtSecurityTokenHandler(); return tokenHandler.WriteToken(token); } private JwtSecurityToken CreateJwtToken(List<Claim> claims, SigningCredentials credentials, DateTime expiration) => new JwtSecurityToken( "apiWithAuthBackend", "apiWithAuthBackend", claims, expires: expiration, signingCredentials: credentials ); private List<Claim> CreateClaims(IdentityUser user) { try { var claims = new List<Claim> { new Claim(JwtRegisteredClaimNames.Sub, "TokenForTheApiWithAuth"), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new Claim(JwtRegisteredClaimNames.Iat, DateTime.UtcNow.ToString(CultureInfo.InvariantCulture)), new Claim(ClaimTypes.NameIdentifier, user.Id ?? ""), new Claim(ClaimTypes.Name, user.UserName ?? ""), new Claim(ClaimTypes.Email, user.Email ?? "") }; return claims; } catch (Exception e) { Console.WriteLine(e); throw; } } private SigningCredentials CreateSigningCredentials() { string secretKey = "!SomethingSecret!"; byte[] keyBytes = Encoding.UTF8.GetBytes(secretKey); // Trim or pad the key to make it exactly 32 bytes Array.Resize(ref keyBytes, 32); return new SigningCredentials( new SymmetricSecurityKey(keyBytes), SecurityAlgorithms.HmacSha256 ); } }
Program.cs代码
var builder = WebApplication.CreateBuilder(args); // Add services builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddScoped<IPageService, PageService>(); builder.Services.AddDbContext<UsersContext>(); builder.Services.AddScoped<IAuthService, AuthService>(); builder.Services.AddScoped<ITokenService, TokenService>(); builder.Services.AddDbContext<ProductDataContext>(options => { options.UseSqlServer(builder.Configuration.GetConnectionString("ProductDataContext")); }); // Add Identity builder.Services.AddIdentityCore<IdentityUser>(options => { options.SignIn.RequireConfirmedAccount = false; options.User.RequireUniqueEmail = true; options.Password.RequireDigit = false; options.Password.RequiredLength = 6; options.Password.RequireNonAlphanumeric = false; options.Password.RequireUppercase = false; options.Password.RequireLowercase = false; }).AddEntityFrameworkStores<UsersContext>(); // Configure Swagger builder.Services.AddSwaggerGen(option => { option.SwaggerDoc("v1", new OpenApiInfo { Title = "Demo API", Version = "v1" }); option.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { In = ParameterLocation.Header, Description = "Please enter a valid token", Name = "Authorization", Type = SecuritySchemeType.Http, BearerFormat = "JWT", Scheme = "Bearer" }); option.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type=ReferenceType.SecurityScheme, Id="Bearer" } }, new string[]{} } }); }); // Configure Authentication builder.Services .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters() { ClockSkew = TimeSpan.Zero, ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = "apiWithAuthBackend", ValidAudience = "apiWithAuthBackend", IssuerSigningKey = new SymmetricSecurityKey( PadKey(Encoding.UTF8.GetBytes("!SomethingSecret!"), 32) ), }; }); var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Run();
排查方案
1. 统一密钥处理逻辑
TokenService中用Array.Resize将密钥字节数组调整为32位,但Program.cs的认证配置里调用了未提供实现的PadKey方法。两者处理逻辑必须完全一致,否则签名验证会失败。
修复方式:在Program.cs中使用和TokenService相同的密钥处理逻辑:
// 修改Program.cs中的IssuerSigningKey配置 byte[] keyBytes = Encoding.UTF8.GetBytes("!SomethingSecret!"); Array.Resize(ref keyBytes, 32); IssuerSigningKey = new SymmetricSecurityKey(keyBytes)
2. 调整ClockSkew容错时间
ClockSkew = TimeSpan.Zero会因服务器与客户端的微小时间差触发验证失败,建议改为:
ClockSkew = TimeSpan.FromMinutes(1)
3. 验证令牌内容
用代码解析生成的令牌,确认Issuer、Audience、过期时间等参数是否正确:
var tokenHandler = new JwtSecurityTokenHandler(); var jwtToken = tokenHandler.ReadJwtToken("你的令牌字符串"); Console.WriteLine($"Issuer: {jwtToken.Issuer}"); Console.WriteLine($"Audience: {string.Join(",", jwtToken.Audiences)}"); Console.WriteLine($"Expires: {jwtToken.ValidTo}");
4. 排除Swagger配置问题
直接用Postman等工具携带Authorization: Bearer {你的令牌}请求头访问接口,确认是否是Swagger的授权头格式问题。
5. 确认中间件顺序
当前Program.cs中UseAuthentication()在UseAuthorization()之前,顺序正确,但需确保没有其他自定义中间件拦截认证流程。
内容的提问来源于stack exchange,提问作者bence0601
相关产品推荐
相关产品推荐

