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

如何用JwtBearer认证SignalR客户端并接入Cookie认证的同一Hub?

同一SignalR Hub实现Cookie与JWT双认证方案

1. 完善JWT Bearer认证配置

先在Program.cs里补全JWT的配置,适配SignalR的WebSocket传输特性(控制台客户端通常会把token放在查询参数中):

// 从配置读取JWT参数(建议存放在appsettings.json)
var jwtKey = builder.Configuration["Jwt:Key"] ?? "your_strong_secret_key_here";
var jwtIssuer = builder.Configuration["Jwt:Issuer"] ?? "your_app_issuer";
var jwtAudience = builder.Configuration["Jwt:Audience"] ?? "your_app_audience";

builder.Services.AddAuthentication(options =>
{
    // 默认认证方案设为Cookie,适配Web GUI客户端
    options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
    options.Cookie.Name = "SID";
    options.Cookie.SameSite = SameSiteMode.Strict;
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        ValidIssuer = jwtIssuer,
        ValidAudience = jwtAudience,
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey))
    };

    // 适配SignalR:从查询参数读取token
    options.Events = new JwtBearerEvents
    {
        OnMessageReceived = context =>
        {
            var token = context.Request.Query["access_token"];
            if (!string.IsNullOrEmpty(token))
            {
                context.Token = token;
            }
            return Task.CompletedTask;
        }
    };
});

// 配置Hub同时支持Cookie和JWT两种认证方案
app.MapHub<SocketHub>("/socket", options =>
{
    options.AuthenticationSchemes = new[] 
    {
        CookieAuthenticationDefaults.AuthenticationScheme,
        JwtBearerDefaults.AuthenticationScheme
    };
});

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

2. 实现Hub的认证与用户组逻辑

完善SocketHub代码,处理控制台客户端的用户名密码验证、JWT签发,同时自动将认证用户加入对应用户组:

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.SignalR;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

public class SocketHub : Hub
{
    private readonly IConfiguration _configuration;
    // 注入用户验证服务(替换为你的业务逻辑实现)
    private readonly IUserValidationService _userValidationService;

    public SocketHub(IConfiguration configuration, IUserValidationService userValidationService)
    {
        _configuration = configuration;
        _userValidationService = userValidationService;
    }

    public override async Task OnConnectedAsync()
    {
        // 检查用户是否已通过认证(Web端Cookie/控制台端JWT)
        if (Context.User.Identity?.IsAuthenticated == true)
        {
            var userName = Context.User.Identity.Name;
            // 将当前连接加入以用户名命名的组
            await Groups.AddToGroupAsync(Context.ConnectionId, $"UserGroup_{userName}");
            Console.WriteLine($"用户 {userName} 连接成功,加入组 UserGroup_{userName}");
        }
        else
        {
            Console.WriteLine($"未认证客户端连接,ID: {Context.ConnectionId}");
        }
        await base.OnConnectedAsync();
    }

    // 控制台客户端调用此方法获取JWT
    public async Task<string> Authentication(string username, string password)
    {
        // 验证用户名密码(替换为数据库查询逻辑)
        var isValidUser = await _userValidationService.ValidateUser(username, password);
        if (!isValidUser)
        {
            throw new HubException("用户名或密码错误");
        }

        // 创建用户Claims
        var claims = new List<Claim>
        {
            new Claim(ClaimTypes.Name, username),
            // 可添加角色等其他Claim
            new Claim(ClaimTypes.Role, "Admin")
        };

        // 生成JWT Token
        var jwtKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]));
        var credentials = new SigningCredentials(jwtKey, SecurityAlgorithms.HmacSha256);
        var token = new JwtSecurityToken(
            issuer: _configuration["Jwt:Issuer"],
            audience: _configuration["Jwt:Audience"],
            claims: claims,
            expires: DateTime.Now.AddHours(2),
            signingCredentials: credentials
        );

        var tokenString = new JwtSecurityTokenHandler().WriteToken(token);

        // 验证通过后直接将当前连接加入用户组
        await Groups.AddToGroupAsync(Context.ConnectionId, $"UserGroup_{username}");
        return tokenString;
    }
}

// 示例用户验证服务(实际项目替换为你的业务实现)
public interface IUserValidationService
{
    Task<bool> ValidateUser(string username, string password);
}

public class UserValidationService : IUserValidationService
{
    public async Task<bool> ValidateUser(string username, string password)
    {
        // 这里替换为数据库验证逻辑
        return username == "Admin" && password == "YourPassword123";
    }
}

核心逻辑说明

  • 多认证兼容:通过MapHub指定AuthenticationSchemes,让Hub同时接受Cookie和JWT两种认证方式。
  • JWT适配SignalR:控制台客户端首次无token连接,调用Authentication方法验证后获取token,后续连接可通过access_token查询参数或请求头携带token,服务器自动读取验证。
  • 用户组统一:无论Web端还是控制台端的认证用户,都会在连接时自动加入对应用户名的组,实现同一用户多客户端同组。

控制台客户端连接示例

// 首次无token连接
var connection = new HubConnectionBuilder()
    .WithUrl("http://localhost:5000/socket")
    .Build();

await connection.StartAsync();

// 调用认证方法获取token
var token = await connection.InvokeAsync<string>("Authentication", "Admin", "YourPassword123");

// 使用token重新建立认证连接
var authenticatedConnection = new HubConnectionBuilder()
    .WithUrl("http://localhost:5000/socket", options =>
    {
        options.AccessTokenProvider = () => Task.FromResult(token);
    })
    .Build();

await authenticatedConnection.StartAsync();

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 03:31:37