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

React如何通过请求头发送JWT?.NET 7 API授权问题排查

.NET 7 Web API JWT授权在React请求中失效问题

问题描述

我有一个基于.NET 7的Web API,采用JWT进行身份验证与授权。通过SwaggerUI和Postman测试时,无JWT的请求会被正确拦截,带JWT的请求可正常访问;但通过React发送请求时无法正常工作。

React请求代码

const getTopics = () => {
    const url = API_ROUTES.TOPICS_GET_ALL_TOPICS;
    fetch(url, {
      method: "GET",
      headers: {
        'Authorization': `Bearer ${jwt}`
      }
    })
    .then(res => res.json())
    .then(topicsFromServer => {
      console.log(topicsFromServer);
      setTopics(topicsFromServer);
    })
    .catch((error => {
      console.error(error);
    }))
  }

控制台响应信息

Response {type: 'cors', url: 'https://localhost:7174/Account/Login?ReturnUrl=%2Ftopic%2Fget-all-topics', redirected: true, status: 405, ok: false, …}
body
: 
(...)
bodyUsed
: 
false
headers
: 
Headers {}
ok
: 
false
redirected
: 
true
status
: 
405
statusText
: ""
type
: "cors"
url
: "https://localhost:7174/Account/Login?ReturnUrl=%2Ftopic%2Fget-all-topics"
[[Prototype]]
: Response

该响应中的URL并非存在的端点,推测是.NET的[Authorize]特性自动生成的。

.NET CORS策略配置

builder.Services.AddCors(options =>
{
    options.AddPolicy("CORSPolicy", builder =>
    {
        builder
        .AllowAnyMethod()
        .AllowAnyHeader()
        .WithOrigins("http://localhost:3000", "https://localhost:3000");
    });
});

Swagger生成配置

builder.Services.AddSwaggerGen(options =>
{
    options.AddSecurityDefinition("auth0", new OpenApiSecurityScheme
    {
        In = ParameterLocation.Header,
        Name = "Authorization",
        Type = SecuritySchemeType.ApiKey
    });

    options.OperationFilter<SecurityRequirementsOperationFilter>();
});

补充说明

进一步测试后发现,JWT并未被实际使用。用户登录后,浏览器(SwaggerUI)或Postman中会存储一个Cookie,无需JWT即可访问API。

使用的Cookie:

.AspNetCore.Identity.Application=ReallyLongString; Path=/; Secure; HttpOnly; Expires=Fri, 31 Mar 2023 23:07:00 GMT;

Program.cs完整代码

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("CORSPolicy", builder =>
    {
        builder
        .AllowAnyMethod()
        .AllowAnyHeader()
        .WithOrigins("http://localhost:3000", "https://localhost:3000", "https://appname.asurestaticapps.net");
    });
});
// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
    options.AddSecurityDefinition("auth0", new OpenApiSecurityScheme
    {
        In = ParameterLocation.Header,
        Name = "Authorization",
        Type = SecuritySchemeType.ApiKey
    });

    options.OperationFilter<SecurityRequirementsOperationFilter>();
});

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters()
        {
            ValidateIssuer = false,
            ValidateAudience = false,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidAudience = builder.Configuration["Jwt:Audience"],
            ValidIssuer = builder.Configuration["Jwt:Issuer"],
            IssuerSigningKey = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])
            )
        };
    });

builder.Services.AddAuthorization();

builder.Services.AddDbContext<SdeResearchDbContext>();
builder.Services.AddIdentity<ApplicationUser, IdentityRole>()
    .AddEntityFrameworkStores<SdeResearchDbContext>()
    .AddDefaultTokenProviders();

builder.Services.AddScoped<JwtService>();

builder.Services.Configure<IdentityOptions>(options =>
{
    // Password Settings.
    options.Password.RequireDigit= true;
    options.Password.RequireLowercase= true;
    options.Password.RequireNonAlphanumeric= true;
    options.Password.RequireUppercase= true;
    options.Password.RequiredLength= 8;
    options.Password.RequireLowercase= true;
    options.Password.RequiredUniqueChars = 6;

    // User settings
    options.User.AllowedUserNameCharacters =
    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+!";
    options.User.RequireUniqueEmail= true;
});

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseRouting();

app.UseCors("CORSPolicy");
app.UseAuthentication();
app.UseAuthorization();


app.MapControllerRoute(
    name: "Topic",
    pattern: "topic/*{action}"
    );

app.Run();

TopicController顶部添加了[Authorize]特性。


解决方案

1. 核心问题定位

你的API同时启用了JWT Bearer认证和ASP.NET Core Identity Cookie认证,但默认情况下,Identity会将未授权请求重定向到登录页(即你看到的/Account/Login),而JWT认证的默认行为是直接返回401而非重定向。

Swagger/Postman能正常工作是因为它们在登录后自动携带了Identity的AspNetCore.Identity.Application Cookie,实际走的是Cookie认证而非JWT;而React请求只带了JWT,却被Identity的重定向逻辑干扰,导致返回405(重定向后的登录页不支持GET请求)。

2. 修复步骤

步骤一:修改JWT认证配置,禁用重定向

在AddJwtBearer配置中添加事件处理,当认证失败时直接返回401,避免被Identity重定向:

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters()
        {
            ValidateIssuer = false,
            ValidateAudience = false,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidAudience = builder.Configuration["Jwt:Audience"],
            ValidIssuer = builder.Configuration["Jwt:Issuer"],
            IssuerSigningKey = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])
            )
        };
        
        // 添加以下代码
        options.Events = new JwtBearerEvents
        {
            OnChallenge = context =>
            {
                // 禁止重定向,直接返回401
                context.HandleResponse();
                context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                context.Response.ContentType = "application/json";
                return context.Response.WriteAsync(JsonSerializer.Serialize(new
                {
                    Message = "未授权访问,请提供有效的JWT令牌"
                }));
            }
        };
    });

步骤二:明确设置默认认证方案(可选但推荐)

由于同时注册了JWT和Identity认证,需要明确指定默认使用JWT Bearer方案,避免Identity Cookie优先生效:

builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options => { /* 原配置 */ });

步骤三:验证React请求的JWT有效性

确保React中携带的jwt变量是完整有效的令牌,没有截断或格式错误。可以在控制台打印jwt值,复制到JWT校验工具中验证签名和有效期。

步骤四:检查CORS配置(可选)

当前CORS配置允许指定Origin的任意头和方法,基本没问题,但如果后续遇到OPTIONS预检请求问题,可以根据需求调整,JWT场景下无需启用AllowCredentials()。

3. 验证修复

修改配置后重启API,在React中重新发送请求:

  • 如果JWT有效,应正常返回数据;
  • 如果JWT无效或未携带,应返回401 JSON响应,而非重定向到登录页。

内容的提问来源于stack exchange,提问作者Morgan Bradford

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 18:40:02