You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

携带JWT调用角色授权GET接口时返回401 Unauthorized问题求助

问题:携带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内容:JWT内容
  • 401响应:401响应
  • GET请求携带JWT: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 06:45:21