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

Azure SignalR客户端连接401错误:invalid_token,签名密钥未找到

Azure SignalR Service 连接401错误:The signature key was not found

问题澄清

微软官方示例默认客户端与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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 16:25:59