ASP.NET+React项目请求用户角色API时出现401未授权错误
ASP.NET + React 登录后请求Roles接口返回401未授权问题排查
问题描述
使用ASP.NET + React开发应用,User类包含3种角色。登录流程正常完成,但登录后请求https://localhost:7260/api/User/Roles时返回401未授权错误。
相关代码
端点代码
[HttpGet("Roles")] [Authorize] public async Task<IActionResult> GetUserRoles() { var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (userIdClaim == null || !int.TryParse(userIdClaim, out var userId)) { return Unauthorized(new { Message = "Invalid user token." }); } var roles = await _userService.GetUserRolesAsync(userId); if (roles == null) { return NotFound(new { Message = "User not found." }); } return Ok(new { Roles = roles }); }
App.jsx(认证相关代码)
const [isAuthModalOpen, setAuthModalOpen] = useState(false); const [isLoggedIn, setIsLoggedIn] = useState(false); const [userRoles, setUserRoles] = useState([]); // 组件挂载时从localStorage加载认证状态 useEffect(() => { const token = localStorage.getItem("jwtToken"); console.log("Token retrieved from localStorage:", token); // 调试输出 if (token) { validateToken(token); } }, []); const validateToken = async (token) => { console.log("Token during validation:", token); // 调试输出 try { const response = await axios.get("https://localhost:7260/api/User/Roles", { headers: { Authorization: `Bearer ${token}` }, }); setIsLoggedIn(true); setUserRoles(response.data.Roles || []); // 使用API返回的角色 } catch (error) { console.error("Failed to validate token:", error); handleLogout(); // 令牌无效则登出 } }; const handleLoginClick = () => { setAuthModalOpen(true); }; const handleAuthSuccess = (jwtToken) => { console.log("Token on successful login:", jwtToken); // 调试输出 setIsLoggedIn(true); localStorage.setItem("jwtToken", jwtToken); // 将JWT令牌存储到localStorage setAuthModalOpen(false); validateToken(jwtToken); }; const handleLogout = () => { setIsLoggedIn(false); localStorage.removeItem("jwtToken"); setUserRoles([]); };
Program.cs中的JWT配置
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = builder.Configuration["JwtSettings:Issuer"], ValidAudiences = new[] { builder.Configuration["JwtSettings:Audience"] }, IssuerSigningKey = new SymmetricSecurityKey( Encoding.UTF8.GetBytes(builder.Configuration["JwtSettings:Secret"]) ) }; });
.csproj文件
<Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>net8.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> <SpaRoot>..\s27707.client</SpaRoot> <SpaProxyLaunchCommand>npm run dev</SpaProxyLaunchCommand> <SpaProxyServerUrl>https://localhost:63283</SpaProxyServerUrl> </PropertyGroup> <ItemGroup> <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.11" /> <PackageReference Include="Microsoft.AspNetCore.Cors" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.11" /> <PackageReference Include="Microsoft.AspNetCore.SpaProxy"> <Version>8.*-*</Version> </PackageReference> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0"> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <PrivateAssets>all</PrivateAssets> </PackageReference> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.0" /> <PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.3.0" /> <PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.3.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="7.2.0" /> <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.3.0" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\s27707.client\s27707.client.esproj"> <ReferenceOutputAssembly>false</ReferenceOutputAssembly> </ProjectReference> </ItemGroup> </Project>
完整Program.cs代码
using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using System.Text; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore.Diagnostics; var builder = WebApplication.CreateBuilder(args); // 添加容器服务 builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); // 配置CORS允许前端通信 builder.Services.AddCors(options => { options.AddPolicy("AllowFrontend", policy => { policy.WithOrigins("https://localhost:63283") // 前端URL .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); // 如果使用Cookie或凭证 }); }); // 配置DbContext(SQLite示例) builder.Services.AddDbContext<TravelDbContext>(options => options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection")) .ConfigureWarnings(warnings => warnings.Ignore(RelationalEventId.PendingModelChangesWarning))); // 注册仓储 builder.Services.AddScoped<IPropertyRepository, PropertyRepository>(); builder.Services.AddScoped<IUserRepository, UserRepository>(); builder.Services.AddScoped<IBookingRepository, BookingRepository>(); builder.Services.AddScoped<IAmenityRepository, AmenityRepository>(); // 注册服务 builder.Services.AddScoped<IPropertyService, PropertyService>(); builder.Services.AddScoped<IUserService, UserService>(); builder.Services.AddScoped<IBookingService, BookingService>(); builder.Services.AddScoped<IAmenityService, AmenityService>(); // 注册JWT令牌生成器 builder.Services.AddScoped<IJwtTokenGenerator, JwtTokenGenerator>(); // 注册认证服务 builder.Services.AddScoped<IAuthService, AuthService>(); // 注册AutoMapper builder.Services.AddAutoMapper(typeof(Program)); // 配置Identity builder.Services.AddIdentity<User, Role>() .AddEntityFrameworkStores<TravelDbContext>() .AddDefaultTokenProviders(); builder.Services.Configure<IdentityOptions>(options => { options.Password.RequireDigit = false; options.Password.RequireLowercase = false; options.Password.RequireNonAlphanumeric = false; options.Password.RequireUppercase = false; options.Password.RequiredLength = 6; options.Password.RequiredUniqueChars = 1; }); // 配置认证和JWT Bearer builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { AuthenticationType = "JwtSettings", ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = builder.Configuration["JwtSettings:Issuer"], //ValidAudience = builder.Configuration["Jwt: Audience"], ValidAudiences = new[] { builder.Configuration["JwtSettings:Audience"] }, IssuerSigningKey = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(builder.Configuration["JwtSettings:Secret"])) }; }); var app = builder.Build(); // 种子角色 using (var scope = app.Services.CreateScope()) { var services = scope.ServiceProvider; try { await RoleSeeder.SeedRolesAsync(services); } catch (Exception ex) { Console.WriteLine($"Error seeding roles: {ex.Message}"); } } // 配置HTTP请求管道 if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseCors("AllowFrontend"); app.UseRouting(); app.UseAuthentication(); // 确保添加认证中间件 app.UseAuthorization(); app.MapControllers(); app.Run();
可能的原因及解决方案
1. 包版本冲突
csproj中EF Core使用9.0.0版本,但其他ASP.NET相关包为8.0.11,版本不匹配可能导致身份验证异常。
修复:将EF Core相关包版本统一为8.0.11:
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.11" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.11"> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <PrivateAssets>all</PrivateAssets> </PackageReference> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.11" />
2. JWT令牌验证参数不匹配
检查appsettings.json中的JwtSettings配置(Issuer、Audience、Secret)是否与生成令牌时的参数完全一致。尤其注意ValidAudiences是数组,生成令牌时需确保Audience包含对应值。
3. 令牌生成时缺失NameIdentifier声明
如果JWT生成逻辑中未添加ClaimTypes.NameIdentifier(用户ID)声明,后端无法获取userIdClaim,会返回401。
修复:在令牌生成代码中添加该声明:
var claims = new List<Claim> { new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), // 其他必要声明 };
4. AuthenticationType配置冲突
Program.cs中JWT验证参数设置了AuthenticationType = "JwtSettings",与Identity默认的Identity.Application可能冲突。
修复:移除该配置项:
options.TokenValidationParameters = new TokenValidationParameters { // 移除 AuthenticationType = "JwtSettings", ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = builder.Configuration["JwtSettings:Issuer"], ValidAudiences = new[] { builder.Configuration["JwtSettings:Audience"] }, IssuerSigningKey = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(builder.Configuration["JwtSettings:Secret"])) };
5. 令牌格式或请求头错误
- 检查前端请求的Authorization头是否正确:
Bearer与令牌之间必须有空格 - 用jwt.io解析令牌,确认令牌未过期、内容完整
- 在前端捕获详细错误信息,查看401的具体响应内容:
catch (error) { console.error("Status:", error.response.status); console.error("Error message:", error.response.data); handleLogout(); }
6. 启用调试日志排查
在Program.cs中添加JWT认证日志,查看令牌验证的详细错误:
builder.Logging.AddFilter("Microsoft.AspNetCore.Authentication.JwtBearer", LogLevel.Debug);
内容的提问来源于stack exchange,提问作者Nakiro
相关产品推荐
相关产品推荐

