ASP.NET授权无法识别SignalR CONNECT请求中的JWT Bearer令牌
你遇到的这个情况不少开发者都碰过,核心问题大概率出在IConfigureNamedOptions的配置范围或者SignalR请求的特殊处理逻辑上,下面给你拆解原因和解决办法:
可能的原因
命名选项匹配问题
如果你实现IConfigureNamedOptions<JwtBearerOptions>时,只针对特定名称的选项做了配置(比如只处理了name为"CustomBearer"的情况),但SignalR默认使用的是名为"Bearer"的JWT认证方案,这就会导致SignalR的请求对应的JwtBearerOptions没有被你的配置覆盖,自然不会触发OnMessageReceived的逻辑。SignalR请求路径未被识别
你的OnMessageReceived逻辑可能没有针对SignalR的CONNECT请求路径做判断,导致即使查询字符串里有access_token,也没被赋值给context.Token。毕竟SignalR的请求路径一般是/hubs/xxx这类,和普通API路径不同,如果你的逻辑里没有匹配这个路径前缀,就会跳过处理。中间件顺序错误
如果JWT Bearer认证中间件是在SignalR中间件之后添加的,那么SignalR的CONNECT请求会先经过SignalR中间件,此时还没完成认证,就会触发授权质询。
解决办法
1. 确保IConfigureNamedOptions覆盖默认认证方案的选项
实现IConfigureNamedOptions<JwtBearerOptions>时,要同时处理名称为"Bearer"和名称为null的情况(因为默认方案可能使用null作为名称):
public class JwtBearerOptionsConfig : IConfigureNamedOptions<JwtBearerOptions> { public void Configure(string name, JwtBearerOptions options) { // 处理默认的"Bearer"方案,以及未指定名称的情况 if (name == JwtBearerDefaults.AuthenticationScheme || name == null) { ConfigureJwtOptions(options); } } public void Configure(JwtBearerOptions options) { ConfigureJwtOptions(options); } private void ConfigureJwtOptions(JwtBearerOptions options) { options.Events.OnMessageReceived = context => { var accessToken = context.Request.Query["access_token"]; var path = context.HttpContext.Request.Path; // 匹配SignalR的hub路径,替换成你实际的路径前缀 if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/hubs")) { context.Token = accessToken; } return Task.CompletedTask; }; // 其他JWT配置... } }
2. 明确指定SignalR使用的认证方案
在Startup/Program.cs中配置SignalR时,显式指定使用的认证方案为"Bearer",确保SignalR使用的是你配置过的JwtBearerOptions:
app.MapHub<YourHub>("/hubs/yourhub") .RequireAuthorization(options => { options.DefaultPolicy = new AuthorizationPolicyBuilder() .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme) .RequireAuthenticatedUser() .Build(); });
3. 检查中间件顺序
确保认证中间件在SignalR中间件之前添加:
// 先添加认证中间件 app.UseAuthentication(); app.UseAuthorization(); // 再添加SignalR中间件 app.MapHub<YourHub>("/hubs/yourhub");
为什么SignalR的CONNECT请求会被区别对待?
主要是因为SignalR的CONNECT请求属于HTTP升级请求(比如WebSocket的Upgrade请求),它的管道处理流程和普通的REST API请求略有不同:
- 这类请求会先被SignalR的中间件识别,提前进入SignalR的处理流程,如果此时认证还没完成,就会直接触发授权质询。
- 另外,SignalR的请求在某些阶段的上下文传递可能和普通API不同,如果你没有针对性地处理路径,就会导致令牌提取逻辑不生效。
内容的提问来源于stack exchange,提问作者Jonas Rembratt

