信号器核心 (2.1) JWT 身份验证中心/协商 401 未经授权

Jon*_*Jon 6 asp.net-core-signalr

所以我有一个 .net core (2.1) API,它使用 JWT 令牌进行身份验证。我可以登录并成功拨打经过身份验证的电话。

我正在为客户端使用 React (16.6.3),它获取 JWT 代码并对 API 进行经过身份验证的调用。

我正在尝试向站点添加信号集线器。如果我不在集线器类上放置 [Authorize] 属性。我可以连接、发送和接收消息(目前它是一个基本的聊天室)。

当我将 [Authorize] 属性添加到类时,React 应用程序会将 HttpPost 生成到example.com/hubs/chat/negotiate. 我会得到一个401状态码。在Authorization: Bearer abc.....头会向上传递。

要在 React 中构建集线器,我使用:

const hubConn = new signalR.HubConnectionBuilder()
            .withUrl(`${baseUrl}/hubs/chat`, { accessTokenFactory: () => jwt })
            .configureLogging(signalR.LogLevel.Information)
            .build();
Run Code Online (Sandbox Code Playgroud)

其中jwt变量是令牌。

我有一些身份验证设置:

services.AddAuthentication(a =>
{
    a.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    a.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
    options.SaveToken = false;
    options.Audience = jwtAudience;

options.TokenValidationParameters = new TokenValidationParameters
{
    ValidateIssuer = true,
    ValidateAudience = true,
    ValidateLifetime = true,
    ValidateIssuerSigningKey = true,
    ValidIssuer = jwtIssuer,
    ValidAudience = jwtAudience,
    RequireExpirationTime = true,
    IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtKey)),

};

// 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"];
        var authToken = context.Request.Headers["Authorization"].ToString();

        var token = !string.IsNullOrEmpty(accessToken) ? accessToken.ToString() : !string.IsNullOrEmpty(authToken) ?  authToken.Substring(7) : String.Empty;

        var path = context.HttpContext.Request.Path;

        // If the request is for our hub...
        if (!string.IsNullOrEmpty(token) && path.StartsWithSegments("/hubs"))
        {
            // Read the token out of the query string
            context.Token = token;
        }
        return Task.CompletedTask;
    }                    
};
Run Code Online (Sandbox Code Playgroud)

});

OnMessageReceived事件确实被命中并被context.Token设置为 JWT 令牌。

我无法弄清楚我做错了什么才能对信号核心进行经过身份验证的调用。


解决方案

我更新了我的代码以使用 2.2(不确定这是否真的需要)。

所以我花了一些时间查看源代码以及其中的示例:

https://github.com/aspnet/AspNetCore

我有一个 Signalr CORS 问题,已通过以下方式解决:

services.AddCors(options =>
        {
            options.AddPolicy("CorsPolicy",
                builder => builder
                    .AllowAnyMethod()
                    .AllowAnyHeader()
                    .AllowCredentials()
                    .SetIsOriginAllowed((host) => true) //allow all connections (including Signalr)
                );
        });
Run Code Online (Sandbox Code Playgroud)

重要的部分是.SetIsOriginAllowed((host) => true) 这允许网站和信号器 cors 访问的所有连接。

我没有添加

services.AddAuthorization(options =>
    {
        options.AddPolicy(JwtBearerDefaults.AuthenticationScheme, policy =>
        {
            policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
            policy.RequireClaim(ClaimTypes.NameIdentifier);
        });
    });
Run Code Online (Sandbox Code Playgroud)

我只用过 services.AddAuthentication(a =>

我直接从github中的示例中获取了以下内容

            options.Events = new JwtBearerEvents
            {
                OnMessageReceived = context =>
                {
                    var accessToken = context.Request.Query["access_token"];

                    if (!string.IsNullOrEmpty(accessToken) &&
                        (context.HttpContext.WebSockets.IsWebSocketRequest || context.Request.Headers["Accept"] == "text/event-stream"))
                    {
                        context.Token = context.Request.Query["access_token"];
                    }
                    return Task.CompletedTask;
                }
            };   
Run Code Online (Sandbox Code Playgroud)

不确定属性中是否需要它,但同样在其集线器上使用它

    [Authorize(JwtBearerDefaults.AuthenticationScheme)]
Run Code Online (Sandbox Code Playgroud)

有了这个,我无法让多个网站和控制台应用程序通过信号器进行连接和通信。

Mar*_*sch 6

要与[Authorize]您一起使用,您需要设置请求标头。由于 Web 套接字不支持标头,因此令牌与查询字符串一起传递,您可以正确解析该字符串。缺少的一件事是

context.Request.Headers.Add("Authorization", "Bearer " + token);
Run Code Online (Sandbox Code Playgroud)

或者在你的情况下可能 context.HttpContext.Request.Headers.Add("Authorization", "Bearer " + token);

例子:

这就是我的做法。在客户端:

const signalR = new HubConnectionBuilder().withUrl(`${this.hubUrl}?token=${token}`).build();
Run Code Online (Sandbox Code Playgroud)

在服务器上,在 Startup.Configure 中:

app.Use(async (context, next) => await AuthQueryStringToHeader(context, next));
// ...
app.UseSignalR(r => r.MapHub<SignalRHub>("/hubUrl"));
Run Code Online (Sandbox Code Playgroud)

AuthQueryStringToHeader 的实现:

private async Task AuthQueryStringToHeader(HttpContext context, Func<Task> next)
{
    var qs = context.Request.QueryString;

    if (string.IsNullOrWhiteSpace(context.Request.Headers["Authorization"]) && qs.HasValue)
    {
        var token = (from pair in qs.Value.TrimStart('?').Split('&')
                     where pair.StartsWith("token=")
                     select pair.Substring(6)).FirstOrDefault();

        if (!string.IsNullOrWhiteSpace(token))
        {
            context.Request.Headers.Add("Authorization", "Bearer " + token);
        }
    }

    await next?.Invoke();
}
Run Code Online (Sandbox Code Playgroud)

  • 警告:如果令牌过期,当您直接将其添加到 url 中时,signalR 服务将无法更新它。实现 accessTokenFactory,这是在套接字 url 中设置令牌的正确方法。 (2认同)