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

自定义认证授权下SignalR与多应用集成的身份验证方案咨询

自定义认证与SignalR集成防身份冒充方案

针对你的场景,核心问题是禁止前端传递发送者身份,改为后端从已认证上下文获取真实用户,结合不同应用的认证特性,提供以下可行方案:

方案一:从SignalR上下文提取已认证用户(推荐)

直接移除SendMessage方法中的user1参数,发送者身份由后端从SignalR连接的认证上下文自动获取,彻底杜绝前端冒充可能。

1. 改造Hub的SendMessage方法

public async Task SendMessage(string user2, string message)
{
    // 从SignalR上下文获取当前已认证用户的标识(用户名/用户ID)
    var currentUser = Context.User?.Identity?.Name;
    if (string.IsNullOrEmpty(currentUser))
    {
        throw new HubException("未认证用户无法发送消息");
    }

    // 额外校验:确认用户存在于你的自定义用户表中
    var userExists = await _customUserService.CheckUserExists(currentUser);
    if (!userExists)
    {
        throw new HubException("无效的用户身份");
    }

    // 执行消息发送逻辑
    await Clients.User(user2).SendAsync("ReceiveMessage", currentUser, message);
}

2. 适配不同应用的认证传递

  • Asp.net MVC/Web Form:
    若使用自定义Forms认证,SignalR会自动共享当前会话的认证Cookie,Context.User会直接获取到登录用户。如果是自定义认证票据,需确保认证中间件覆盖SignalR的连接请求(在Startup中先配置认证,再配置SignalR)。
  • Windows桌面应用:
    无法共享Web端Cookie,需在连接SignalR时传递认证令牌:
    1. 用户登录后,调用你的自定义认证服务获取短期有效令牌;
    2. 连接SignalR时,将令牌通过QueryString或请求头传递;
    3. 在Hub的OnConnectedAsync中验证令牌并绑定用户:
      public override async Task OnConnectedAsync()
      {
          // 从QueryString获取令牌
          var token = Context.GetHttpContext().Request.Query["authToken"].ToString();
          if (string.IsNullOrEmpty(token))
          {
              await Context.AbortAsync();
              return;
          }
      
          // 调用自定义认证服务验证令牌,获取用户信息
          var userInfo = await _customAuthService.ValidateToken(token);
          if (userInfo == null)
          {
              await Context.AbortAsync();
              return;
          }
      
          // 将用户信息注入SignalR上下文
          var identity = new ClaimsIdentity(new[]
          {
              new Claim(ClaimTypes.Name, userInfo.UserName),
              new Claim(ClaimTypes.NameIdentifier, userInfo.UserId.ToString())
          });
          Context.User = new ClaimsPrincipal(identity);
      
          // 可选:绑定ConnectionId与用户ID,方便后续群发/定向推送
          await _connectionStore.AddConnection(userInfo.UserId, Context.ConnectionId);
          await base.OnConnectedAsync();
      }
      

方案二:请求签名验证(兼容特殊场景)

如果因业务限制必须保留user1参数,可通过请求签名验证发送者身份的真实性:

1. 前端逻辑

用户登录后,从自定义认证服务获取专属签名密钥;发送消息时,生成签名并随请求传递:

// 示例:前端生成签名(伪代码)
const rawData = `${user1}${user2}${message}${timestamp}`;
const signature = hmacSha256(rawData, userSecretKey);
// 调用SendMessage时传递timestamp和signature

2. 后端验证逻辑

public async Task SendMessage(string user1, string user2, string message, string timestamp, string signature)
{
    // 校验请求是否过期(防止重放攻击)
    var requestTime = DateTimeOffset.FromUnixTimeSeconds(long.Parse(timestamp)).UtcDateTime;
    if (DateTime.UtcNow - requestTime > TimeSpan.FromMinutes(5))
    {
        throw new HubException("请求已过期");
    }

    // 从数据库获取user1的签名密钥
    var userSecret = await _customUserService.GetUserSecret(user1);
    if (string.IsNullOrEmpty(userSecret))
    {
        throw new HubException("用户不存在或无权限");
    }

    // 重新计算签名并校验
    var rawData = $"{user1}{user2}{message}{timestamp}";
    var computedSignature = ComputeHmacSha256(rawData, userSecret);
    if (computedSignature != signature)
    {
        throw new HubException("身份验证失败,禁止冒充");
    }

    // 执行消息发送
    await Clients.User(user2).SendAsync("ReceiveMessage", user1, message);
}

private string ComputeHmacSha256(string data, string key)
{
    using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key));
    var hashBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
    return Convert.ToBase64String(hashBytes);
}

方案三:ConnectionId与用户绑定

在用户连接SignalR时完成身份验证,将ConnectionId与真实用户ID绑定存储,发送消息时通过ConnectionId反向获取发送者:

// 生产环境建议用Redis等分布式缓存替代内存字典
private static readonly Dictionary<string, string> _connectionToUser = new();

public override async Task OnConnectedAsync()
{
    // 身份验证逻辑(同方案一)
    string currentUser = GetAuthenticatedUser();
    if (currentUser == null)
    {
        await Context.AbortAsync();
        return;
    }

    // 绑定ConnectionId与用户
    lock (_connectionToUser)
    {
        _connectionToUser[Context.ConnectionId] = currentUser;
    }
    await base.OnConnectedAsync();
}

public async Task SendMessage(string user2, string message)
{
    lock (_connectionToUser)
    {
        if (!_connectionToUser.TryGetValue(Context.ConnectionId, out var currentUser))
        {
            throw new HubException("用户未连接或身份无效");
        }
        await Clients.User(user2).SendAsync("ReceiveMessage", currentUser, message);
    }
}

关键注意事项

  1. 所有验证逻辑必须在后端完成,绝对不能信任前端传递的任何身份标识;
  2. Windows应用的令牌需设置短有效期(如15分钟),并支持刷新机制,避免令牌泄露后被滥用;
  3. 生产环境中,Connection与用户的映射建议用分布式缓存(Redis),避免单点内存存储的限制和重启丢失问题;
  4. 可结合自定义角色权限,额外校验发送者是否有权限给接收者发送消息(若有业务需求)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 23:05:26