ASP.NET Core SignalR基础认证问题:Socket连接无法通过身份验证
咱们先直击核心:你的问题主要是服务端中间件顺序配错了,导致SignalR的连接请求根本没经过认证中间件,哪怕客户端带了Authorization头也白搭。另外得先搞明白:SignalR的WebSocket连接是先通过HTTP握手建立的,认证信息只需要在这个握手阶段传递就行,后续的WebSocket消息不用再带请求头——只要握手过了,整个连接就会被标记为已认证状态。
第一步:修正Startup的中间件顺序
在Configure方法里,app.UseAuthentication()必须放在app.UseSignalR()前面!你现在的代码里UseSignalR在认证中间件之前,等于SignalR的请求直接绕开了认证流程,Hub上的[Authorize]自然会拒绝连接。
修正后的Configure代码如下:
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseCors("CorsPolicy"); // 重复的UseCors可以删掉,一个就够了 // app.UseCors(CorsConstants.AnyOrigin); app.UseFileServer(); // 认证中间件一定要放在SignalR前面! app.UseAuthentication(); app.UseSignalR(route => { route.MapHub<MessageHub>("/chat"); }); }
ConfigureServices里的配置是没问题的,不过可以把重复的CORS配置去掉,避免冲突。
第二步:确认客户端的认证头传递
你的客户端代码已经正确在连接时添加了Basic Auth的Authorization头,这部分完全没问题:
var credential = Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes("SomeUserName" + ":" + "SomePassword")); var connection = new HubConnectionBuilder() .WithUrl("http://localhost:5000/chat", options => { options.Headers.Add("Authorization", $"Basic {credential}"); }) .AddJsonProtocol() .Build();
SignalR会把这里配置的头放到握手的HTTP请求里发送,服务端只要中间件顺序对了,就会自动验证这个头,通过后[Authorize]就会放行连接。
关于"Socket消息携带令牌"的补充方案
如果你之后想换成自定义令牌(比如JWT),或者需要在连接建立后再做验证,这里给两个可选方案:
方案1:用Bearer Token(推荐)
把Basic Auth换成JWT认证,客户端在握手时传递Authorization: Bearer {你的JWT令牌}头,服务端配置JWT认证中间件——同样要保证中间件顺序在SignalR之前,逻辑和Basic Auth类似,只是认证方式换了。
方案2:连接后发送身份信息验证
如果必须在连接建立后验证身份,可以在Hub里加一个Authenticate方法,客户端连接成功后立刻调用这个方法传令牌,验证不通过就直接断开连接:
服务端Hub修改:
[AllowAnonymous] public class MessageHub : Hub { private static readonly HashSet<string> ConnectedIds = new HashSet<string>(); private static readonly HashSet<string> AuthenticatedIds = new HashSet<string>(); public override async Task OnConnectedAsync() { ConnectedIds.Add(Context.ConnectionId); await Clients.All.SendAsync("SendAction", "joined", ConnectedIds.Count); } public async Task Authenticate(string token) { // 这里写你的令牌验证逻辑,比如查数据库/缓存 if (IsValidToken(token)) { AuthenticatedIds.Add(Context.ConnectionId); await Clients.Caller.SendAsync("AuthenticationSuccess"); } else { await Clients.Caller.SendAsync("AuthenticationFailed"); await Context.Connection.AbortAsync(); } } public override async Task OnDisconnectedAsync(Exception ex) { ConnectedIds.Remove(Context.ConnectionId); AuthenticatedIds.Remove(Context.ConnectionId); await Clients.All.SendAsync("SendAction", "left", ConnectedIds.Count); } // 可以用[Authorize]或者自己检查是否在已认证列表里 public async Task Send(MessagePayload message) { if (!AuthenticatedIds.Contains(Context.ConnectionId)) { throw new HubException("请先完成认证"); } await Clients.All.SendAsync("SendMessage", message); } private bool IsValidToken(string token) { // 替换成你的实际验证逻辑 return token == "your-valid-token"; } }
客户端调用认证:
await connection.StartAsync(); // 连接成功后马上发认证请求 await connection.InvokeAsync("Authenticate", "your-token");
不过这个方案不如握手阶段认证高效安全——握手失败直接就拒绝连接了,不用建立连接后再断开。
验证修复效果
改完中间件顺序后重启服务端和客户端,应该就能正常连接了。你还可以在Hub里通过Context.User拿到认证后的用户信息,比如:
public override async Task OnConnectedAsync() { var userName = Context.User.Identity.Name; // 可以在这里处理用户的连接逻辑,比如记录在线用户 ConnectedIds.Add(Context.ConnectionId); await Clients.All.SendAsync("SendAction", "joined", ConnectedIds.Count); }
内容的提问来源于stack exchange,提问作者Node.JS

