
最近在练习做一个 Web 开发项目,需要使用 WebSockets 传输数据,实现实时通信。这是一个 React.js 项目,后端是 .NET。
虽然 MSDN 提供了出色的顶级文档,但它通常缺少高级用例所需的低级细节。
一种这样的场景是使用自定义令牌对 SignalR Hub 进行身份验证。是的,自定义令牌,而不是 JWT 或默认 Bearer 令牌。本文探讨如何实现这一点。最后,您将拥有一个需要身份验证并使用自定义令牌的 SignalR Hub。
自定义Token
我们将使用的自定义令牌是 Base64 编码的用户信息分隔字符串,格式如下:
userId:userName
从这个标记中,我们将提取userId并userName创建声明。
项目设置
以下是设置项目的基本步骤:
创建一个.NET项目:
dotnet new webapi
添加 SignalR 服务:
 在Program.cs文件中,构建应用程序时注册 SignalR 服务:
 builder.Services.AddSignalR();
创建 Hub :
 创建一个名为 的目录hubs,并添加一个名为 的文件GameHub.cs。执行以下操作:
 public class GameHub : Hub
 {
    public override Task OnConnectedAsync()
    {
        return base.OnConnectedAsync();
    }
   public override Task OnDisconnectedAsync(Exception? exception)
    {
        return base.OnDisconnectedAsync(exception);
    }
 }
映射中心:
 将其GameHub作为端点公开Program.cs:
 app.MapHub<GameHub>("/hubs/game");
实现自定义令牌认证
 要使用自定义令牌并从中提取用户信息,我们需要一个自定义身份验证方案。在 .NET 中,身份验证方案是一个命名标识符,它指定用于对用户进行身份验证的方法或协议,例如 Cookie、JWT 持有者令牌或 Windows 身份验证。对于此场景,我们将创建一个名为的方案CustomToken。
自定义身份验证方案实现
定义自定义令牌方案选项:
 public class CustomTokenSchemeOptions : AuthenticationSchemeOptions
 {
    public CustomTokenSchemeOptions()
    {
        Events = new CustomTokenEvents();
    }
   public new CustomTokenEvents Events
    {
        get => (CustomTokenEvents)base.Events!;
        set => base.Events = value;
    }
 }
定义方案处理程序:包含验证令牌和提取用户声明的逻辑
 :CustomTokenSchemeHandler
 public class CustomTokenSchemeHandler : AuthenticationHandler<CustomTokenSchemeOptions>
 {
    private new CustomTokenEvents Events => (CustomTokenEvents)base.Events!;
   public CustomTokenSchemeHandler(
        IOptionsMonitor<CustomTokenSchemeOptions> options,
        ILoggerFactory logger,
        UrlEncoder encoder) : base(options, logger, encoder) {}
   protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        var messageReceivedContext = new MessageReceivedContext(Context, Scheme, Options);
        await Events.MessageReceivedAsync(messageReceivedContext);
var token = messageReceivedContext.Token ?? GetTokenFromQuery();
       if (token is null)
        {
            return AuthenticateResult.NoResult();
        }
       byte[] data = Convert.FromBase64String(token);
        string decodedString = Encoding.UTF8.GetString(data);
        string[] userInfoArray = decodedString.Split(":");
       var claims = new[]
        {
            new Claim(ClaimTypes.Name, userInfoArray[1]),
            new Claim(ClaimTypes.Sid, userInfoArray[0])
        };
        var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, Scheme.Name));
        var ticket = new AuthenticationTicket(principal, Scheme.Name);
        return AuthenticateResult.Success(ticket);
    }
   private string? GetTokenFromQuery()
    {
        var accessToken = Context.Request.Query["access_token"].ToString();
        return string.IsNullOrEmpty(accessToken) ? null : accessToken;
    }
 }
 配置身份验证方案:
 在以下位置注册自定义身份验证方案Program.cs:
 builder.Services.AddAuthentication("CustomToken")
    .AddScheme<CustomTokenSchemeOptions, CustomTokenSchemeHandler>("CustomToken", opts =>
    {
        opts.Events = new CustomTokenEvents
        {
            OnMessageReceived = context =>
            {
                var accessToken = context.Request.Query["access_token"];
                var path = context.HttpContext.Request.Path;
               if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/hubs/game"))
                {
                    context.Token = accessToken;
                }
               return Task.CompletedTask;
            };
        };
    });
免责声明
本文介绍的实现灵感来自 .NET 官方存储库中的 BearerTokenScheme 源代码。我们对其进行了调整以适应此场景的自定义令牌要求。
总结
通过实施此自定义令牌身份验证方案,您可以保护 SignalR 中心并根据应用程序的独特要求定制身份验证过程。此方法允许对令牌验证和声明提取进行细粒度控制,从而确保安全可靠的实时通信系统。
欢迎随意扩展此实现,添加额外的验证、日志记录或与外部身份提供商的集成,以获得更全面的解决方案。
如果您喜欢此文章,请收藏、点赞、评论,谢谢,祝您快乐每一天。
