ASP.NET Core 7 Web API JWT认证持续返回401未授权问题排查
我在ASP.NET Core 7 Web API中实现JWT认证,调用Login接口能正常获取token,但用Postman把token加到授权头访问其他接口时,一直收到401未授权错误。以下是我的代码实现:
Models
public class ApplicationUser : IdentityUser { public string FirstName { get; set; } public string LastName { get; set; } public virtual ICollection<RefreshToken> RefreshTokens { get; set; } public ICollection<ApplicationUserRole> UserRoles { get; set; } } public class RefreshToken { //[JsonIgnore] public Guid Id { get; set; } public string UserId { get; set; } public string Token { get; set; } public DateTime ExpiryDate { get; set; } public virtual ApplicationUser User { get; set; } }
TokenGeneratorService
public class TokenGeneratorService : ITokenGeneratorService { private readonly IConfiguration _configuration; private readonly UserManager<ApplicationUser> _userManager; public TokenGeneratorService(IConfiguration configuration, UserManager<ApplicationUser> userManager) { _configuration = configuration; _userManager = userManager; } public async Task<string> GenerateToken(ApplicationUser user) { var authClaims = new List<Claim> { new Claim(ClaimTypes.NameIdentifier, user.Id), new Claim(ClaimTypes.Email, user.Email), new Claim(ClaimTypes.Name, user.UserName) //new Claim(ClaimTypes.GivenName, user.FirstName), //new Claim(ClaimTypes.Surname, user.LastName) //new Claim(ClaimTypes.Uri, avatar) }; //Gets the roles of the logged in user and adds it to Claims var roles = await _userManager.GetRolesAsync(user); foreach (var role in roles) { authClaims.Add(new Claim(ClaimTypes.Role, role)); } var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JwtSettings:SecretKey"])); // Specifying JWTSecurityToken Parameters var token = new JwtSecurityToken (audience: _configuration["JwtSettings:Audience"], issuer: _configuration["JwtSettings:Issuer"], claims: authClaims, expires: DateTime.Now.AddHours(2), signingCredentials: new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256)); return new JwtSecurityTokenHandler().WriteToken(token); } public int? ValidateJwtToken(string token) { if (token == null) return null; var tokenHandler = new JwtSecurityTokenHandler(); var key = Encoding.ASCII.GetBytes(_configuration["JwtSettings:SecretKey"]); try { tokenHandler.ValidateToken(token, new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(key), ValidateIssuer = false, ValidateAudience = false, ClockSkew = TimeSpan.Zero }, out SecurityToken validatedToken); var jwtToken = (JwtSecurityToken)validatedToken; var userId = int.Parse(jwtToken.Claims.First(x => x.Type == "id").Value); return userId; } catch { return null; } } public Guid GenerateRefreshToken() { return Guid.NewGuid(); } }
TokenRepository
public class TokenRepository : ITokenRepository { private readonly ApplicationDbContext _dbContext; public TokenRepository(ApplicationDbContext dbContext) { _dbContext = dbContext; } public async Task<ApplicationUser> GetUserByRefreshToken(Guid token, string userId) { // Check for user Id var user = await _dbContext.ApplicationUsers.SingleOrDefaultAsync(u => u.RefreshToken == token && u.Id == userId); //var user = await _context.Users.SingleOrDefault(u => u.RefreshToken == token && u.Id == userId); //FirstOrDefaultAsync if (user == null) { throw new ArgumentException($"User with Id {userId} does not exist"); } return user; } }
Login方法
public async Task<Response<LoginResponseDto>> Login(LoginRequestDto model) { var response = new Response<LoginResponseDto>(); var getUser = await _userManager.FindByNameAsync(model.UserName); var loggedUser = _dbContext.ApplicationUsers.Where(u => u.UserName == model.UserName).Select(m => m.UserName).FirstOrDefault(); var user1 = await _userManager.FindByNameAsync(model.UserName); var loggedUserRole = await _userManager.GetRolesAsync(getUser); var loggedUserRoleName = loggedUserRole[0].ToString(); if (loggedUserRoleName == "Customer" || loggedUserRoleName == "CustomerUser") { var validityResult = await ValidateUser(model); if (!validityResult.Successful) { response.Successful = false; return response; } var user = await _userManager.FindByNameAsync(model.UserName); var roles = await _userManager.GetRolesAsync(user); var refreshToken = _tokenGenerator.GenerateRefreshToken(); user.RefreshToken = refreshToken; user.RefreshTokenExpiryTime = DateTime.Now.AddDays(7); //sets refresh token for 7 days user.LastLogin = DateTime.Now; var result = new LoginResponseDto() { Id = user.Id, Token = await _tokenGenerator.GenerateToken(user), RefreshToken = refreshToken, User = _mapper.Map<UserDto>(user), Roles = roles, Expires = DateTime.Now.AddHours(2) }; await _userManager.UpdateAsync(user); return response; } else { return response; } }
扩展类
public static class AuthenticationServiceExtension { public static void ConfigureAuthentication(this IServiceCollection services, IConfiguration configuration) { var tokenValidationParameters = new TokenValidationParameters { ValidateAudience = true, ValidateIssuer = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidAudience = configuration["JwtSettings:Audience"], ValidIssuer = configuration["JwtSettings:Issuer"], IssuerSigningKey = new SymmetricSecurityKey(Encoding .UTF8.GetBytes(configuration["JwtSettings:SecretKey"])), ClockSkew = TimeSpan.Zero }; services.AddSingleton(tokenValidationParameters); services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options => { options.SaveToken = true; options.TokenValidationParameters = tokenValidationParameters; }); } } public static class DIServiceExtension { public static void AddDependencyInjection(this IServiceCollection services) { // Add Service Injections Here -- Auth services.AddScoped<IAuthService, AuthService>(); services.AddScoped<ITokenGeneratorService, TokenGeneratorService>(); services.AddScoped<ITokenRepository, TokenRepository>(); } }
Program.cs
builder.Services.ConfigureAuthentication(configuration); builder.Services.AddDependencyInjection(); var app = builder.Build(); // Configure the HTTP request pipeline. app.UseSwagger(); app.UseSwaggerUI(); app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); app.UseHsts(); app.UseCors(x => x .AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader()); app.UseStaticFiles(); app.MapControllers(); string? port = Environment.GetEnvironmentVariable("PORT"); if (!string.IsNullOrWhiteSpace(port)) { app.Urls.Add("http://*:" + port); } app.Run();
问题排查与修复方案
1. Token验证编码不一致
TokenGeneratorService中生成token用Encoding.UTF8处理密钥,但ValidateJwtToken方法用了Encoding.ASCII,若密钥含非ASCII字符会导致验证失败。
修复:
将ValidateJwtToken中的编码改为UTF8:
var key = Encoding.UTF8.GetBytes(_configuration["JwtSettings:SecretKey"]);
2. Claim类型匹配错误
生成token时用ClaimTypes.NameIdentifier(对应JWT的nameid),但验证时查找"id"类型的Claim,会导致找不到值抛出异常。同时ApplicationUser.Id是string类型,用int.Parse会报错。
修复:
修改验证逻辑的Claim类型,并调整ID解析方式:
var userId = jwtToken.Claims.First(x => x.Type == ClaimTypes.NameIdentifier).Value; // 若需要返回int类型,需确保User.Id是可转换的数字,否则直接返回string
3. Login方法未正确赋值响应结果
Login方法生成result后,未将其赋值给response.Data,也未设置response.Successful = true,可能导致响应数据异常。
修复:
在返回前补充响应赋值:
response.Successful = true; response.Data = result; return response;
4. 中间件顺序错误
Program.cs中UseCors位置在认证之后,不符合官方推荐顺序,可能影响请求处理。
修复:
调整中间件顺序为:
app.UseSwagger(); app.UseSwaggerUI(); app.UseHsts(); app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseCors(x => x .AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader()); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers();
5. Postman授权头格式检查
确保Postman中授权头为Authorization: Bearer <token>,注意Bearer后有空格,token无多余空格或引号。
6. JWT配置项一致性检查
确认appsettings.json中JwtSettings:Issuer和JwtSettings:Audience的值,与生成、验证token时使用的完全一致,无拼写错误。
内容的提问来源于stack exchange,提问作者Ayobamilaye

