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

ASP.NET Core中带HttpOnly Cookie认证的SignalR连接CORS预检失败

解决SignalR Hub带[Authorize]时的CORS与认证问题

问题根源

核心问题是OPTIONS预请求被JWT认证拦截返回401,导致CORS响应头无法正常添加;同时SignalR协商请求需要正确提取HttpOnly Cookie中的JWT令牌完成认证。

解决方案

按以下步骤修改配置:


1. 调整中间件顺序(关键)

确保UseCors在认证、授权中间件之前执行,保证即使请求被拦截,CORS头也能正确返回:

// Program.cs
var app = builder.Build();

// 必须放在UseAuthentication、UseAuthorization之前
app.UseCors("AllowSpecificOrigins");

app.UseAuthentication();
app.UseAuthorization();

// 注册SignalR端点
app.MapHub<NotificationHub>("/api/NotificationHub");

app.Run();

2. 修改JWTBearer配置,跳过OPTIONS请求的认证挑战

在JWTBearer事件中,对OPTIONS请求直接返回200,避免触发401认证失败:

services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateIssuerSigningKey = true,
        ValidateLifetime = true,
        ValidIssuer = SecretsManager.GetSecret("Jwt:Issuer"),
        ValidAudience = SecretsManager.GetSecret("Jwt:Audience"),
        IssuerSigningKey = new SymmetricSecurityKey(
            Encoding.UTF8.GetBytes(SecretsManager.GetSecret("Jwt:Key"))
        )
    };

    options.Events = new JwtBearerEvents
    {
        OnMessageReceived = ctx =>
        {
            // 从HttpOnly Cookie提取JWT
            ctx.Request.Cookies.TryGetValue("access_token", out var accessToken);
            
            // 确保SignalR协商请求能获取到令牌
            var path = ctx.HttpContext.Request.Path;
            if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/api/NotificationHub"))
            {
                ctx.Token = accessToken;
            }

            return Task.CompletedTask;
        },
        OnChallenge = ctx =>
        {
            // OPTIONS预请求直接返回200,不触发认证挑战
            if (ctx.Request.Method == HttpMethod.Options.Method)
            {
                ctx.Response.StatusCode = StatusCodes.Status200OK;
                // 手动添加必要的CORS头(与CORS策略保持一致)
                ctx.Response.Headers.Append("Access-Control-Allow-Origin", ctx.Request.Headers["Origin"]);
                ctx.Response.Headers.Append("Access-Control-Allow-Credentials", "true");
                return Task.CompletedTask;
            }
            return ctx.HandleChallengeAsync();
        }
    };
});

3. 为Hub指定明确的认证方案

在NotificationHub的[Authorize]属性中指定JWT认证方案,避免歧义:

using Microsoft.AspNetCore.Authentication.JwtBearer;

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[HubEndpoint("api/NotificationHub")]
public class NotificationHub : Hub
{
    // ... 原有代码
}

4. 验证CORS配置的允许源

确保SecretsManager.GetSecret("Cors:AllowedOrigins")返回的列表包含前端地址http://localhost:5173,格式为逗号分隔(无空格):

http://localhost:5173

验证

修改完成后重启后端,前端重新发起SignalR连接:

  • OPTIONS预请求会返回200,且包含正确的CORS头
  • SignalR协商请求会成功提取Cookie中的JWT完成认证,建立连接

内容的提问来源于stack exchange,提问作者b. tsutskiridze

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 23:42:02