繁体   English   中英

JWT 身份验证失败 SignalR

[英]JWT Authentication Failed with SignalR

我有一个 REST API(核心 2.1)需要支持 SPA,使用 SignalR 提供宁静的端点和一些实时交互功能;

Hub/MVC 路由在同一台服务器上运行,该服务器也是 JWT 令牌的提供者。

登录后,客户端会收到一个 JWT 令牌,对于每个 REST 请求,它都会放置在 header 上,否则会收到 401(这与[Authorize]属性一起使用)。

在客户端,下面的代码尝试连接到我的 /hub 端点: new HubConnectionBuilder().withUrl(HUB_URL, { accessTokenFactory: () => this.getToken() })
如果我将[Authorize]放在我的 Hub class 上,我会收到以下错误(没有授权,客户端可以正确发送和收听):

WebSocket 连接到 'wss://localhost:5001/hub?id=MY_ID&access_token=MY_TOKEN' 失败:HTTP 身份验证失败; 没有可用的有效凭据

服务器记录失败的身份验证:

(在 AddJwtBearerOptions.Events.OnMessageReceived 上触发 console.log)
信息:Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
请求开始 HTTP/1.1 GET https://localhost:5001/hub ? id=MY_ID&access_token=MY_TOKEN
信息:Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]
授权失败。
信息:Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler[12]
AuthenticationScheme:Bearer 受到挑战。
信息:Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
请求在 0.3658 毫秒内完成 401

与那些请求不同,使用 SAME JWT TOKEN 和 REST(使用[Authorize] ),使用Header: Bearer XXXX而不是查询字符串,触发OnTokenValidated 永远不会触发OnAuthenticationFailed ,即使身份验证失败:

(在 AddJwtBearerOptions.Events.OnMessageReceived 上触发 console.log)
信息:Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
请求开始 HTTP/1.1 GET https://localhost:5001/api/products application/json
(在 AddJwtBearerOptions.Events.OnTokenValidated 上触发了 console.log)
信息:Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler[2]
成功验证令牌。


检查下面我的“Startup.cs”

ConfigureServices(IServiceCollection)

services.AddSignalR();
services.AddCors(option => option.AddPolicy("CorsPolicy", p => p.AllowAnyHeader().AllowAnyOrigin().AllowAnyMethod().AllowCredentials()));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => {
    options.TokenValidationParameters = new TokenValidationParameters{
        ValidateIssuer = true,
        ValidIssuer = Configuration["JWT:Issuer"],
        ValidateLifetime = true,
        ValidateAudience = false,
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JWT:SecurityKey"]))
    };
});

Configure(IApplicationBuilder)

app.UseAuthentication();
app.UseCors("CorsPolicy");
app.UseHttpsRedirection();
app.UseMvc();
app.UseSignalR(routes => {
     routes.MapHub<ApplicationHub>("/hub");
});

您还需要在.AddJwtBearer部分中添加此块:

// We have to hook the OnMessageReceived event in order to
// allow the JWT authentication handler to read the access
// token from the query string when a WebSocket or 
// Server-Sent Events request comes in.
options.Events = new JwtBearerEvents
{
    OnMessageReceived = context =>
    {
        var accessToken = context.Request.Query["access_token"];

        // If the request is for our hub...
        var path = context.HttpContext.Request.Path;
        if (!string.IsNullOrEmpty(accessToken) &&
            (path.StartsWithSegments("/hub")))
        {
            // Read the token out of the query string
            context.Token = accessToken;
        }
        return Task.CompletedTask;
    }
};

这可以在文档中找到。

info:Microsoft.AspNetCore.Authorization.DefaultAuthorizationService [2]授权失败。 info:Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler [12] AuthenticationScheme:Bearer受到挑战。 info:Microsoft.AspNetCore.Hosting.Internal.WebHost [2]申请已完成0.3658ms 401

花20个小时来修理。 :(原因是:

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class ChatHub : Hub<IChatClient>

我的服务配置:

services.AddAuthentication(options =>
{
     options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
     options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options => ...)

UPD: 这里有JS客户端的完整工作示例。

为了扩展@marcusturewicz已经陈述的内容,还应该检查Authorization header 中的令牌,以防访问令牌是在 header 而不是查询中发送的。

虽然,这可能与客户端实现者发送令牌的方式不同。

options.Events = new JwtBearerEvents
{
    OnMessageReceived = context =>
    {
        // If no token is present in the query, we can
        // try to get it from the Authorization header.
        StringValues accessToken = context.Request.Query["access_token"] == StringValues.Empty
                ? context.Request.Headers["Authorization"]
                : context.Request.Query["access_token"];

        // If the request is for our hub...
        var path = context.HttpContext.Request.Path;
        if (!string.IsNullOrEmpty(accessToken) &&
            (path.StartsWithSegments("/hub")))
        {
            // Read the token out of the query string
            context.Token = accessToken;
        }
        return Task.CompletedTask;
    }
};

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM