携带JWT调用角色授权GET接口时返回401 Unauthorized问题求助
请求头携带JWT调用GET接口时收到401未授权响应,单角色授权同样无法生效,相关代码及信息如下:
SecurityService.cs
创建时生成随机密钥,从User实例获取角色构建JWT Token:
private const int TokenExpirationInSeconds = 600; private readonly IConfiguration _configuration; public byte[] Key { get; } = RandomNumberGenerator.GetBytes(128); public string BuildJwtToken(User user) { var roles = new List<Role> {user.Role}; var roleClaims = roles.ToDictionary( q => ClaimTypes.Role, q => (object) q.Name.ToUpper()); var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(new[] { new Claim(JwtRegisteredClaimNames.Sub, user.UserName), new Claim(JwtRegisteredClaimNames.UniqueName, user.UserName), new Claim(JwtRegisteredClaimNames.Aud, _configuration["Jwt:Audience"] ?? string.Empty), new Claim(JwtRegisteredClaimNames.Iss, _configuration["Jwt:Issuer"] ?? string.Empty), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) }), Expires = DateTime.UtcNow.AddSeconds(TokenExpirationInSeconds), SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Key), SecurityAlgorithms.HmacSha512Signature), Claims = roleClaims }; var tokenHandler = new JwtSecurityTokenHandler(); var token = tokenHandler.CreateToken(tokenDescriptor); var ret = tokenHandler.WriteToken(token); return ret; }
Program.cs
将SecurityService注册为单例,配置Authentication与Authorization:
var builder = WebApplication.CreateBuilder(args); builder.Services.AddSingleton<IConfiguration>(builder.Configuration); SecurityService securityService = new(builder.Configuration); builder.Services.AddDbContext<WorkItDbContext>(); builder.Services.AddSingleton<SecurityService>(); builder.Services.AddTransient<UserService>(); builder.Services.AddTransient<RoleService>(); builder.Services.AddTransient<OfferService>(); builder.Services.AddTransient<CategoryService>(); builder.Services.AddTransient<ResponseService>(); builder.Services.AddTransient<LocationService>(); builder.Services.AddCors(); builder.Services.AddControllers().AddNewtonsoftJson(options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore ); builder.Services.AddAuthorization(); builder.Services.AddAuthentication(opt => { opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; opt.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(opt => { var key = new SymmetricSecurityKey(securityService.Key); opt.TokenValidationParameters = new TokenValidationParameters() { IssuerSigningKey = key, ValidateIssuer = false, ValidateAudience = false, ValidateIssuerSigningKey = true, ValidateLifetime = true, ClockSkew = TimeSpan.Zero }; }); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseSwagger(); app.UseSwaggerUI(); app.UseCors(options => options.WithOrigins("http://localhost:4200/").AllowAnyMethod().AllowAnyHeader().AllowAnyOrigin()); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Run();
CustomRoles.cs
定义自定义角色常量:
public static class CustomRoles { public const string Admin = "ADMIN"; public const string Recruiter = "RECUITER"; public const string User = "USER"; }
UserController.cs
GET接口配置多角色授权:
[HttpGet("All")] [Authorize(Roles = CustomRoles.Admin + "," + CustomRoles.User + "," + CustomRoles.Recruiter)] public async Task<IActionResult> GetUsers() { return Ok(await _userService.GetUsers()); }
Role.cs
角色实体类定义:
public class Role { public Role() { Users = new HashSet<User>(); } public long RoleId { get; set; } public string? Name { get; set; } public virtual ICollection<User> Users { get; set; } }
相关截图
- JWT内容:

- 401响应:

- GET请求携带JWT:

问题原因及解决办法
1. SecurityService实例不一致导致签名验证失败
Program.cs中手动创建了一个SecurityService实例用于JWT验证,但注册单例时用的是容器默认创建的实例,两者的Key是不同的随机值,Token签名验证必然失败,返回401。
解决:修改注册逻辑,确保验证和生成Token用同一个实例:
// 替换原手动创建和注册代码 var securityService = new SecurityService(builder.Configuration); builder.Services.AddSingleton<SecurityService>(securityService);
2. 角色Claim格式不符合授权要求
当前代码用Dictionary添加角色Claim,会生成单个Claim包含角色值,但ASP.NET Core的[Authorize(Roles)]需要多个独立的ClaimTypes.Role类型Claim,每个对应一个角色。另外注意CustomRoles中Recruiter拼写为RECUITER,需和数据库中角色名完全匹配。
解决:修改BuildJwtToken方法,将角色作为独立Claim添加到ClaimsIdentity:
public string BuildJwtToken(User user) { var claims = new List<Claim> { new Claim(JwtRegisteredClaimNames.Sub, user.UserName), new Claim(JwtRegisteredClaimNames.UniqueName, user.UserName), new Claim(JwtRegisteredClaimNames.Aud, _configuration["Jwt:Audience"] ?? string.Empty), new Claim(JwtRegisteredClaimNames.Iss, _configuration["Jwt:Issuer"] ?? string.Empty), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), // 添加独立的角色Claim new Claim(ClaimTypes.Role, user.Role.Name.ToUpper()) }; var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(claims), Expires = DateTime.UtcNow.AddSeconds(TokenExpirationInSeconds), SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Key), SecurityAlgorithms.HmacSha512Signature) }; var tokenHandler = new JwtSecurityTokenHandler(); var token = tokenHandler.CreateToken(tokenDescriptor); return tokenHandler.WriteToken(token); }
3. 优化中间件配置(潜在问题)
代码中重复调用了UseSwagger和UseSwaggerUI,保留环境判断中的调用即可;同时确保中间件顺序正确(当前顺序已正确,但冗余代码可清理):
if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseCors(options => options.WithOrigins("http://localhost:4200/").AllowAnyMethod().AllowAnyHeader().AllowAnyOrigin()); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers();
4. 验证JWT有效性
用JWT解码工具检查Token:
- 签名是否与SecurityService的Key匹配
- 过期时间是否未到期
- 角色Claim是否存在且值与CustomRoles一致
内容的提问来源于stack exchange,提问作者Ondřej Halata
相关产品推荐
相关产品推荐

