ASP.NET Core中JWT负载畸形及受保护路由401问题求助
ASP.NET Core JWT认证问题排查与解决
问题描述
- 生成的JWT负载随机出现畸形,仅少数情况能在jwt.io上正确解析;
- 即使是解析正常的有效JWT,访问受保护路由时仍返回401 Unauthorized。
已尝试多种教程及官方文档未解决,相关代码如下:
Program.cs
// ./Program.cs using System.Text; using BackendAPI.Hubs; using BackendAPI.Models; using BackendAPI.Services; using DotNetEnv; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; var builder = WebApplication.CreateBuilder(args); Env.Load(); string CONNECTION_STRING = Environment.GetEnvironmentVariable("DEFAULT_CONNECTION")!; string jwtToken = Environment.GetEnvironmentVariable("JWT_TOKEN")!; string validIssuer = Environment.GetEnvironmentVariable("VALID_ISSUER")!; string validAudience = Environment.GetEnvironmentVariable("VALID_AUDIENCE")!; builder.Services.AddControllers(); builder.Services.AddDbContext<ScalpersParadiseContext>(options => options.UseNpgsql(CONNECTION_STRING)); builder.Services.AddControllers(); builder.Services.AddScoped<IReservationNotificationService, ReservationNotificationService>(); builder.Services.AddCors(options => { options.AddPolicy("AllowSignalR", policy => { policy.WithOrigins("http://localhost:3000") .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); }); }); builder.Services.AddSignalR(); builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = validIssuer, ValidateAudience = true, ValidAudience = validAudience, ValidateLifetime = true, IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(jwtToken) ), ValidateIssuerSigningKey = true }; }); builder.Services.AddScoped<IAuthService, AuthService>(); var app = builder.Build(); app.UseRouting(); app.UseCors("AllowSignalR"); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.MapHub<ReservationHub>("/hubs/reservations"); app.Run();
AuthController.cs
// ./Controllers/AuthController.cs using BackendAPI.Models; using BackendAPI.Models.DataTransferObjects; using BackendAPI.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace BackendAPI.Controllers { [ApiController] [Route("api/[controller]")] public class AuthController(IAuthService authService) : ControllerBase { [HttpPost("register")] public async Task<ActionResult<User>> Register(UserDto request) { var user = await authService.RegisterAsync(request); if (user is null) { return BadRequest("User already exists."); } return Ok(user); } [HttpPost("login")] public async Task<ActionResult<string>> Login(UserDto request) { var token = await authService.LoginAsync(request); if (token is null) { return BadRequest("Invalid username or password"); } return Ok(token); } [HttpPost("refresh-token")] public async Task<ActionResult<TokenResponseDto>> RefreshToken(RefreshTokenRequestDto request) { var result = await authService.RefreshTokensAsync(request); if (result is null || result.AccessToken is null || result.RefreshToken is null) { return Unauthorized("Invalid Refresh Token."); } return Ok(result); } [Authorize] [HttpGet("pleasework")] public IActionResult AuthenticatedOnlyEndpoint() { return Ok("You are authenticated!"); } [Authorize(Roles = "Admin")] [HttpGet("admin-only")] public IActionResult AdminOnlyEndpoint() { return Ok("You are an admin!"); } } }
AuthService.cs
// ./Services/AuthService.cs using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using BackendAPI.Models; using BackendAPI.Models.DataTransferObjects; using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; namespace BackendAPI.Services { public class AuthService(ScalpersParadiseContext context) : IAuthService { private readonly string jwtToken = Environment.GetEnvironmentVariable("JWT_TOKEN")!; private readonly string validIssuer = Environment.GetEnvironmentVariable("VALID_ISSUER")!; private readonly string validAudience = Environment.GetEnvironmentVariable("VALID_AUDIENCE")!; public async Task<User?> RegisterAsync(UserDto request) { if (await context.Users.AnyAsync(u => u.Username == request.Username)) { return null; } var user = new User(); var hashedPassword = new PasswordHasher<User>() .HashPassword(user, request.Password); user.UserId = Guid.NewGuid(); user.Username = request.Username; user.Email = request.Email; user.PasswordHash = hashedPassword; user.CreatedAt = DateTime.UtcNow; user.Role = "Standard"; context.Users.Add(user); await context.SaveChangesAsync(); return user; } public async Task<TokenResponseDto?> LoginAsync(UserDto request) { var user = await context.Users.FirstOrDefaultAsync(u => u.Username == request.Username); if (user is null) { return null; } if (new PasswordHasher<User>().VerifyHashedPassword(user, user.PasswordHash, request.Password) == PasswordVerificationResult.Failed) { return null; } return await CreateTokenResponse(user); } private async Task<TokenResponseDto> CreateTokenResponse(User user) { return new TokenResponseDto { AccessToken = CreateToken(user), RefreshToken = await GenerateAndSaveRefreshTokenAsync(user), }; } public async Task<TokenResponseDto?> RefreshTokensAsync(RefreshTokenRequestDto request) { var user = await ValidateRefreshTokenAsync(request.UserId, request.RefreshToken); if (user is null) { return null; } return await CreateTokenResponse(user); } public async Task<User?> ValidateRefreshTokenAsync(Guid userId, string refreshToken) { var user = await context.Users.FindAsync(userId); if (user is null || user.RefreshToken != refreshToken || user.RefreshTokenExpiryTime <= DateTime.UtcNow) { return null; } return user; } private string GenerateRefreshToken() { var randomNumber = new byte[32]; using var rng = RandomNumberGenerator.Create(); rng.GetBytes(randomNumber); return Convert.ToBase64String(randomNumber); } private async Task<string> GenerateAndSaveRefreshTokenAsync(User user) { var refreshToken = GenerateRefreshToken(); user.RefreshToken = refreshToken; user.RefreshTokenExpiryTime = DateTime.UtcNow.AddDays(7); await context.SaveChangesAsync(); return refreshToken; } private string CreateToken(User user) { var claims = new List<Claim> { new Claim(ClaimTypes.Name, user.Username), new Claim(ClaimTypes.NameIdentifier, user.UserId.ToString()), new Claim(ClaimTypes.Role, user.Role), }; var key = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(jwtToken) ); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512); var tokenDescriptor = new JwtSecurityToken( issuer: validIssuer, audience: validAudience, claims: claims, expires: DateTime.UtcNow.AddDays(1), signingCredentials: creds ); return new JwtSecurityTokenHandler().WriteToken(tokenDescriptor); } } }
问题排查与解决
问题1:JWT负载随机畸形
- 环境变量读取异常:检查
JWT_TOKEN、VALID_ISSUER、VALID_AUDIENCE是否包含换行、空格等特殊字符,读取时添加Trim()处理:string jwtToken = Environment.GetEnvironmentVariable("JWT_TOKEN")!.Trim(); string validIssuer = Environment.GetEnvironmentVariable("VALID_ISSUER")!.Trim(); string validAudience = Environment.GetEnvironmentVariable("VALID_AUDIENCE")!.Trim(); - 密钥长度不符合要求:HmacSha512要求密钥至少64字节,检查
JWT_TOKEN长度,不足则更换更长的随机字符串。
问题2:有效JWT访问受保护路由返回401
- 统一环境变量读取逻辑:当前
Program.cs和AuthService重复读取环境变量,可能导致值不一致。建议在Program.cs中将变量注入服务,避免重复读取。 - 添加角色声明映射:在JWT Bearer配置中明确角色声明类型,确保角色验证生效:
.AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { // 原有参数... RoleClaimType = ClaimTypes.Role }; // 新增日志排查验证失败原因 options.Events = new JwtBearerEvents { OnAuthenticationFailed = context => { Console.WriteLine($"认证失败:{context.Exception.Message}"); return Task.CompletedTask; } }; }); - 检查请求头格式:确保请求时使用
Authorization: Bearer <token>格式,无多余空格或字符。 - 验证Token生成参数一致性:确认生成JWT时的
issuer、audience、密钥与认证配置完全一致,包括大小写和特殊字符。
额外建议
- 在
AuthService.CreateToken()中打印生成的Token内容,方便排查畸形问题; - 使用
JwtSecurityTokenHandler.ValidateToken()手动验证Token,定位验证逻辑问题。
内容的提问来源于stack exchange,提问作者Bruh Momento
相关产品推荐
相关产品推荐

