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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 03:29:51