首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >AspNet.Core,IdentityServer 4:在使用JWT承载令牌与SignalR 1.0进行websocket握手期间未经授权的(401)

AspNet.Core,IdentityServer 4:在使用JWT承载令牌与SignalR 1.0进行websocket握手期间未经授权的(401)
EN

Stack Overflow用户
提问于 2018-06-01 09:31:08
回答 1查看 6.1K关注 0票数 13

我有两个aspnet.core服务。一个用于IdentityServer 4,另一个用于Angular4+客户端使用的API。SignalR集线器在API上运行。整个解决方案运行在码头上,但这不重要(见下文)。

我使用的是隐含的欧斯流程,它工作得完美无缺。NG应用程序重定向到用户登录的IdentityServer登录页面。之后,浏览器将被重定向回带有访问令牌的NG应用程序。然后使用令牌调用API并与SignalR建立通信。我想我已经阅读了所有可用的内容(见下面的资料)。

由于SignalR使用的是不支持标头的websockets,所以应该在查询字符串中发送令牌。然后,在API端,提取令牌并为请求设置,就像在标头中一样。然后对令牌进行验证,并授权用户。

API的工作没有任何问题,用户获得了授权,并且可以在API端检索声明。所以IdentityServer应该没有问题,因为SignalR不需要任何特殊的配置。我说的对吗?

当我没有在SignalR集线器上使用授权属性时,握手成功了。这就是为什么我认为我使用的对接器基础设施和反向代理没有什么问题(该代理被设置为启用websockets)。

因此,未经授权,SignalR可以工作。经过授权,NG客户端在握手期间得到以下响应:

代码语言:javascript
复制
Failed to load resource: the server responded with a status of 401
Error: Failed to complete negotiation with the server: Error
Error: Failed to start the connection: Error

请求是

代码语言:javascript
复制
Request URL: https://publicapi.localhost/context/negotiate?signalr_token=eyJhbGciOiJSUz... (token is truncated for simplicity)
Request Method: POST
Status Code: 401 
Remote Address: 127.0.0.1:443
Referrer Policy: no-referrer-when-downgrade

我得到的回应是:

代码语言:javascript
复制
access-control-allow-credentials: true
access-control-allow-origin: http://localhost:4200
content-length: 0
date: Fri, 01 Jun 2018 09:00:41 GMT
server: nginx/1.13.10
status: 401
vary: Origin
www-authenticate: Bearer

根据日志,令牌被成功地验证了。我可以包括完整的日志,但我怀疑问题出在哪里。因此,我将把这部分包括在这里:

代码语言:javascript
复制
[09:00:41:0561 Debug] Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler AuthenticationScheme: Identity.Application was not authenticated.
[09:00:41:0564 Debug] Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler AuthenticationScheme: Identity.Application was not authenticated.

我在日志文件中得到了这些信息,我不知道这意味着什么。我在API中包括代码部分,在API中获取和提取令牌以及身份验证配置。

代码语言:javascript
复制
services.AddAuthentication(options =>
    {
        options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultForbidScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultSignOutScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    }).AddIdentityServerAuthentication(options =>
        {
            options.Authority = "http://identitysrv";
            options.RequireHttpsMetadata = false;
            options.ApiName = "publicAPI";
            options.JwtBearerEvents.OnMessageReceived = context =>
            {
                if (context.Request.Query.TryGetValue("signalr_token", out StringValues token))
                {
                    context.Options.Authority = "http://identitysrv";
                    context.Options.Audience = "publicAPI";
                    context.Token = token;
                    context.Options.Validate();
                }

                return Task.CompletedTask;
            };
        });

系统中没有其他错误和异常。我可以调试这个应用程序,一切看起来都很好。

包含的日志行意味着什么?如何调试授权期间发生的事情?

编辑:我几乎忘记提到的,我认为问题在于身份验证方案,因此,我将每个方案设置为我认为需要的方案。然而,遗憾的是,这并没有帮助。

我在这里有点不知所措,所以我很感激任何建议。谢谢。

资料来源:

将令牌传递给SignalR

用SignalR保护IdentityServer

关于SignalR授权的Microsoft文档

另一个GitHub问题

对SignalR进行认证

Identity.Application没有被认证

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2018-06-02 16:54:49

我必须回答我自己的问题,因为我有最后期限,而且令人惊讶的是,我设法解决了这个问题。所以我把它写下来,希望它能帮助将来的人。

首先,我需要对正在发生的事情有一些了解,所以我将整个授权机制替换为自己的授权机制。我可以用这个代码来做。这不是解决方案所必需的,但是如果有人需要它,这就是解决方案的方法。

代码语言:javascript
复制
services.Configure<AuthenticationOptions>(options =>
{
    var scheme = options.Schemes.SingleOrDefault(s => s.Name == JwtBearerDefaults.AuthenticationScheme);
    scheme.HandlerType = typeof(CustomAuthenticationHandler);
});

IdentityServerAuthenticationHandler的帮助下,并覆盖所有可能的方法,我最终了解到,OnMessageRecieved事件是在检查令牌之后执行的。因此,如果在调用HandleAuthenticateAsync时没有任何标记,响应将是401。这帮助我找出了把我的自定义代码放在哪里。

在令牌检索期间,我需要实现自己的“协议”。所以我换了那个装置。

代码语言:javascript
复制
services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;                
}).AddIdentityServerAuthentication(JwtBearerDefaults.AuthenticationScheme,
    options =>
    {
        options.Authority = "http://identitysrv";
        options.TokenRetriever = CustomTokenRetriever.FromHeaderAndQueryString;
        options.RequireHttpsMetadata = false;
        options.ApiName = "publicAPI";
    });

重要的部分是TokenRetriever属性以及替换它的内容。

代码语言:javascript
复制
public class CustomTokenRetriever
{
    internal const string TokenItemsKey = "idsrv4:tokenvalidation:token";
    // custom token key change it to the one you use for sending the access_token to the server
    // during websocket handshake
    internal const string SignalRTokenKey = "signalr_token";

    static Func<HttpRequest, string> AuthHeaderTokenRetriever { get; set; }
    static Func<HttpRequest, string> QueryStringTokenRetriever { get; set; }

    static CustomTokenRetriever()
    {
        AuthHeaderTokenRetriever = TokenRetrieval.FromAuthorizationHeader();
        QueryStringTokenRetriever = TokenRetrieval.FromQueryString();
    }

    public static string FromHeaderAndQueryString(HttpRequest request)
    {
        var token = AuthHeaderTokenRetriever(request);

        if (string.IsNullOrEmpty(token))
        {
            token = QueryStringTokenRetriever(request);
        }

        if (string.IsNullOrEmpty(token))
        {
            token = request.HttpContext.Items[TokenItemsKey] as string;
        }

        if (string.IsNullOrEmpty(token) && request.Query.TryGetValue(SignalRTokenKey, out StringValues extract))
        {
            token = extract.ToString();
        }

        return token;
    }

这是我的自定义令牌检索算法,它首先尝试标准的标头和查询字符串,以支持常见的情况,如web调用。但是,如果令牌仍然是空的,则尝试从客户端在websocket握手过程中放置的查询字符串中获取令牌。

编辑:我使用以下客户端(TypeScript)代码来提供SignalR握手的令牌

代码语言:javascript
复制
import { HubConnection, HubConnectionBuilder, HubConnectionState } from '@aspnet/signalr';
// ...
const url = `${apiUrl}/${hubPath}?signalr_token=${accessToken}`;
const hubConnection = new HubConnectionBuilder().withUrl(url).build();
await hubConnection.start();

其中apiUrl、hubPath和accessToken是连接所需的参数。

票数 21
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/50640316

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档