ASP.NET Core Web API中JWT认证遇401未授权问题排查
在ASP.NET Core Web API中实现JWT认证后,调用GetUserId等需要用户身份的接口时遭遇401 Unauthorized错误,仅login和register接口能正常工作。尝试添加/移除[Authorize]特性均无法解决问题,需要获取当前用户的UserId。
UserController代码
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Identity; using System.Threading.Tasks; using LostAndFoundBack.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using Microsoft.Extensions.Configuration; using System; namespace LostAndFoundBack.Controllers { [ApiController] [Route("api/[controller]")] public class UserController : ControllerBase { private readonly UserManager<User> _userManager; private readonly RoleManager<IdentityRole> _roleManager; private readonly SignInManager<User> _signInManager; private readonly IConfiguration _configuration; public UserController(UserManager<User> userManager, RoleManager<IdentityRole> roleManager, SignInManager<User> signInManager, IConfiguration configuration) { _userManager = userManager; _roleManager = roleManager; _signInManager = signInManager; _configuration = configuration; } [HttpPost("register")] public async Task<IActionResult> Register(RegisterModel model) { if (ModelState.IsValid) { var user = new User { UserName = model.Name, Email = model.Email }; var result = await _userManager.CreateAsync(user, model.Password); if (result.Succeeded) { await _userManager.AddToRoleAsync(user, "User"); return Ok(new { UserId = user.Id, UserName = user.UserName, Email = user.Email }); } foreach (var error in result.Errors) { ModelState.AddModelError(string.Empty, error.Description); } return BadRequest(ModelState); } return BadRequest(ModelState); } [HttpPost("login")] public async Task<IActionResult> Login(LoginModel model) { var user = await _userManager.FindByEmailAsync(model.Email); if (user != null && await _userManager.CheckPasswordAsync(user, model.Password)) { var token = GenerateJwtToken(user); return Ok(new { Token = token }); } return Unauthorized(); } [Authorize] [HttpGet("get-roles")] public async Task<IActionResult> GetUserRoles() { var user = await _userManager.GetUserAsync(User); if (user == null) { return Unauthorized(); } var roles = await _userManager.GetRolesAsync(user); return Ok(roles); } [Authorize] [HttpGet("user-information")] public async Task<IActionResult> GetCurrentUser() { var user = await _userManager.GetUserAsync(User); if (user == null) { return Unauthorized(); } return Ok(new { user.Id, user.UserName, user.Email }); } [HttpGet("user-id")] public async Task<IActionResult> GetUserId() { var user = await _userManager.GetUserAsync(User); if (user == null) { return Unauthorized(); } return Ok(new { user.Id }); } private string GenerateJwtToken(User user) { var claims = new[] { new Claim(JwtRegisteredClaimNames.Sub, user.UserName), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new Claim(ClaimTypes.NameIdentifier, user.Id) }; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"])); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: _configuration["Jwt:Issuer"], audience: _configuration["Jwt:Audience"], claims: claims, expires: DateTime.Now.AddMinutes(30), signingCredentials: creds); return new JwtSecurityTokenHandler().WriteToken(token); } } }
Program.cs代码
using LostAndFoundBack.DataBase; using LostAndFoundBack.Models; using LostAndFoundBack.Repositories.Interfaces; using LostAndFoundBack.Repositories; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using System.Text; using Microsoft.OpenApi.Models; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "LostAndFoundBack API", Version = "v1" }); // Configure Swagger to use JWT Authentication c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { In = ParameterLocation.Header, Description = "Please enter into field the word 'Bearer' followed by a space and the JWT value", Name = "Authorization", Type = SecuritySchemeType.ApiKey, BearerFormat = "JWT", Scheme = "Bearer" }); c.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } }, new string[] {} } }); }); builder.Services.AddAuthorization(); builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = builder.Configuration["Jwt:Issuer"], ValidAudience = builder.Configuration["Jwt:Audience"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])) }; }); builder.Services.AddIdentityCore<User>(options => { options.User.RequireUniqueEmail = true; }) .AddRoles<IdentityRole>() .AddSignInManager<SignInManager<User>>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders(); // Configure DbContext with SQL Server and enable retry on failure builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"), sqlServerOptionsAction: sqlOptions => { sqlOptions.EnableRetryOnFailure( maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); })); // Configure CORS builder.Services.AddCors(options => { options.AddPolicy("AllowFrontendApp", policy => policy.WithOrigins("http://localhost:3000") .AllowAnyMethod() .AllowAnyHeader() .AllowCredentials()); }); builder.Services.AddScoped<IItemRepository, ItemRepository>(); var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "LostAndFoundBack API v1"); }); } // Configure the HTTP request pipeline. app.UseHttpsRedirection(); app.UseCors("AllowFrontendApp"); // Ensure CORS is used before UseRouting/UseAuthentication/UseAuthorization app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Run();
问题排查与解决方案
1. 给GetUserId接口添加[Authorize]特性
当前GetUserId接口未标记[Authorize],导致请求不会触发JWT认证,User对象为空,直接返回401。修改后代码:
[Authorize] [HttpGet("user-id")] public async Task<IActionResult> GetUserId() { var user = await _userManager.GetUserAsync(User); if (user == null) { return Unauthorized(); } return Ok(new { user.Id }); }
2. 验证JWT配置一致性
检查appsettings.json中的JWT配置项,确保Jwt:Key、Jwt:Issuer、Jwt:Audience与Program.cs及GenerateJwtToken方法中的值完全一致:
Jwt:Key长度需至少16字符(HmacSha256要求),避免因密钥过短导致验证失败- 配置项大小写敏感,需完全匹配
3. 确认请求头格式正确
调用接口时,必须在请求头中携带正确格式的Authorization字段:
Authorization: Bearer {你的JWT Token}
注意Bearer与Token之间有一个空格,Swagger测试时需通过认证按钮输入正确格式的Token。
4. 检查User类主键类型
确保User类的Id类型(如string)与GenerateJwtToken中添加的ClaimTypes.NameIdentifier存储的类型一致,_userManager.GetUserAsync依赖该Claim来匹配数据库中的用户。
5. 验证中间件顺序
当前Program.cs中的中间件顺序符合ASP.NET Core要求:UseCors → UseRouting → UseAuthentication → UseAuthorization → MapControllers
无需调整,但若后续添加其他中间件,需保证认证/授权中间件在路由之后、控制器映射之前。
内容的提问来源于stack exchange,提问作者jashman

