Azure SignalR客户端连接401错误:invalid_token,签名密钥未找到
问题澄清
微软官方示例默认客户端与Hub代码同属一个ASP.NET Core应用,但我的场景是Hub代码为独立ASP.NET Core应用,客户端部署在另一独立ASP.NET应用中。
问题详情
原本本地SignalR服务运行正常,现在集成Azure SignalR Service管理连接。客户端为JavaScript,服务器端Hub基于ASP.NET Core 9。Hub与Azure SignalR Service连接正常(Live Trace Tool可查连接记录),但客户端获取JWT令牌后连接Azure SignalR Service时返回401 - 未授权,DevTools显示Www-Authenticate头错误:Bearer error='invalid_token', error_description='The signature key was not found'。已确认Hub代码与令牌生成器的JWT签名密钥一致。
服务器端Hub代码(program.cs)
var builder = WebApplication.CreateBuilder(args); // Add services to the container. var connectionString = builder.Configuration.GetConnectionString("AzureSignalRConnectionString"); //builder.Services.AddCors(); builder.Services.AddSignalR().AddAzureSignalR(connectionString); builder.Services.AddAuthorization(options => { options.AddPolicy("AuthenticatedUsers", policy => { policy.RequireAuthenticatedUser(); }); }); builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = "lobbycentral.com", ValidAudience = "https://lobbycentral.service.signalr.net", IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["TokenKey"])) }; options.Events = new JwtBearerEvents { OnMessageReceived = context => { var token = context.Request.Query["token"]; var path = context.HttpContext.Request.Path; if (!string.IsNullOrEmpty(token) && path.StartsWithSegments("/hub")) { context.Token = token; } return Task.CompletedTask; } }; }); builder.Services.AddAuthorization(); builder.Services.AddCors(options => options.AddPolicy("testing", policy => { policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin(); })); var app = builder.Build(); app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseCors("testing"); app.UseAuthorization(); app.MapHub<NotificationHub>("NotificationHub"); app.Run();
JWT令牌生成API(localhost)
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(TOKEN_KEY)); var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); var clientID = "user123"; var issuer = "<my issuer>"; var audience = "https://<resource_name>.service.signalr.net"; var claims = new[] { new Claim(JwtRegisteredClaimNames.Sub, clientID), new Claim(JwtRegisteredClaimNames.Iss, issuer), new Claim(JwtRegisteredClaimNames.Aud, audience), new Claim(JwtRegisteredClaimNames.Exp, DateTimeOffset.UtcNow.AddHours(1).ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64), new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64), new Claim(JwtRegisteredClaimNames.Nbf, DateTimeOffset.UtcNow.AddSeconds(-1).ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64), new Claim("role", "client"), }; var token = new JwtSecurityToken( issuer: issuer, audience: audience, claims: claims, expires: DateTime.UtcNow.AddHours(1), signingCredentials: credentials ); return new JwtSecurityTokenHandler().WriteToken(token);
Javascript客户端(index.html)
<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/6.0.1/signalr.js"></script> <script type='text/javascript'> var URL = 'https://<resource_name>.service.signalr.net/client/?hub=NotificationHub'; notifyConnection = new signalR.HubConnectionBuilder() .withUrl(URL, { accessTokenFactory: async () => { const response = await fetch("https://localhost:44328/v1/Authenticate/GetSRToken?companyID=123&clientID=user123"); const data = await response.json(); console.debug('token is ' + data.token); return data.token; } }) .withAutomaticReconnect() .configureLogging(signalR.LogLevel.Information) .build(); console.debug("starting connection"); notifyConnection.start(); </script>
已排查项
- 使用jwt.io验证JWT令牌包含所有必要声明;
- 验证服务器端Hub与令牌生成器的签名密钥一致;
- 验证令牌已生成并存在于Bearer请求头中;
- 已添加CORS策略。
Azure SignalR实例信息
- CORS设置为
*; - 定价层为“免费”;
- 服务模式为“默认”。
解决方案
针对The signature key was not found错误,结合你的场景,可从以下几个方向修复:
1. 补充认证中间件调用
当前代码仅配置了AddAuthentication但未启用认证中间件,导致认证逻辑未生效。调整中间件顺序,添加UseAuthentication:
app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseCors("testing"); app.UseAuthentication(); // 新增该行,确保认证逻辑先执行 app.UseAuthorization(); app.MapHub<NotificationHub>("NotificationHub"); app.Run();
2. 修正受众(Audience)配置一致性
Hub代码中ValidAudience设置为https://lobbycentral.service.signalr.net,但令牌生成时的audience是https://<resource_name>.service.signalr.net,需确保两者完全一致,包括资源名称的拼写和格式。
3. 调整令牌提取路径判断
客户端连接的是Azure SignalR的/client/路径,Hub代码中原有的path.StartsWithSegments("/hub")无法匹配,导致无法提取token。修改路径判断逻辑:
options.Events = new JwtBearerEvents { OnMessageReceived = context => { var token = context.Request.Query["token"]; var path = context.HttpContext.Request.Path; if (!string.IsNullOrEmpty(token) && (path.StartsWithSegments("/NotificationHub") || path.StartsWithSegments("/client"))) { context.Token = token; } return Task.CompletedTask; } };
4. 验证密钥配置加载正确性
在Hub代码中添加日志输出builder.Configuration["TokenKey"]的值,确认密钥是否与令牌生成API的TOKEN_KEY完全一致(注意大小写、特殊字符、空格等细节)。
内容的提问来源于stack exchange,提问作者user27197224

