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

.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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 22:58:11