SignalR AccessTokenProvider 适用于 TypeScript 客户端,但不适用于 .NET 客户端

问题描述 投票:0回答:2

我正在尝试通过 C# .NET 客户端中的

HubConnection
传递 access_token。但是,结果与我通过 TypeScript 客户端看到的结果不一致。这种不一致会导致 C# .NET 客户端中的授权失败,但 TypeScript 客户端中的授权成功。

相关代码如下:

打字稿

var builder = new signalr.HubConnectionBuilder();
builder.withUrl(hubUrl, {accessTokenFactory: () => token});

C#

var builder = new HubConnectionBuilder();
builder.WithUrl(url, o => {
  o.AccessTokenProvider = () => Task.FromResult(_token);
  //I've tried the following as well
  //o.Headers.Add("Authorization", "Bearer " + _token);
});

TypeScript 代码生成一个 HTTP 请求,如下所示:

POST http://localhost:5000/machine/negotiate?negotiateVersion=1 HTTP/1.1
Host: localhost:5000
Connection: keep-alive
Content-Length: 0
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoidGVzdCIsImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvcm9sZSI6IkFkbWluaXN0cmF0b3IiLCJleHAiOjE1NzYxODYwMTYsImlzcyI6Im5TY3J5cHQsIEluYy4iLCJhdWQiOiJuU3R1ZGlvIFVzZXIifQ.qxAzu-NgzlnfCqyysiML4Z0_s6UBTeRb7wcuGno9rk4
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) nstudio-pro/1.0.0 Chrome/78.0.3905.1 Electron/7.0.0 Safari/537.36
Content-Type: text/plain;charset=UTF-8
Accept: */*
Origin: http://localhost:3000
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: cors
Referer: http://localhost:3000/main_window
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US
Cookie: .AspNetCore.Identity.Application=CfDJ8Dp0ZrsdLf1KpYnUb_iitp4gyw5TR5uJhBjiI8pFnzSSEM9ALjY6XbRVrMURJ_NrkK9IAD0Xlqy3l6HEQNJkfGG7vwCRfj5x4NjEW4Msm5GoQMuhG6epa3Q3r8QDhdC4z3tJSS0bRZ_EXvmnnnVWYvw4lILddLABWnf3leeMXrKUmq6AUAJPy1SVgj4fJQ8BGOo5HLPZDLZxN-m3ZV0jUaDkOf_mosTAz7JTjI53bAlxD0hi78YYzkVfpa8dEs8gXOTD85f96_m5DGGoMMCnvsjMP6ST1Q87rWHCCsxUPPLaH_A2xc6JpUqvzV-Pur6KtE8oFmcen4jq7h0kL2akXWUvTApZIxY7lFvFx7x4-8andT1DP7T3tRNdWnoRNRotSoQCp4HtS3Cz0GRwcyaKyhuFBjdUFMj1H0FKDYOEJEiarVMX0bElqgTjGGr7ZiOPyTJq1yHmCOraqdbP7YMycTWfC4F1tPXS0v4KxxNo8F2o31MYlhCx_sTIgEjJUHjdh9iugr401GYzazV3reL4M64YAliZ3fynzXf7ZNVqwUg-OvDzXd0nba4E3BVd_hQDwlssaWYq0DAZvrwO56iUwv1y9e-wehaH6OzocmvujVLX_HYG20BsXN6YdLiPPfhqNdkay50AaTuvXF2kq-exJTEnYBuc9U6eTWn9--mEFXKe4VQlTnn97AGvLfnOt_QrhUK4Pc88Z9q3hHNu7MNhfnE

而 C# 代码创建:

POST http://localhost:5000/machine/negotiate?negotiateVersion=1 HTTP/1.1
Host: localhost:5000
User-Agent: Microsoft.AspNetCore.Http.Connections.Client/3.1.0
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoidGVzdCIsImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvcm9sZSI6IkFkbWluaXN0cmF0b3IiLCJleHAiOjE1NzYxODYxOTIsImlzcyI6Im5TY3J5cHQsIEluYy4iLCJhdWQiOiJuU3R1ZGlvIFVzZXIifQ.i_m-hnyZfPmFoUSX9VHPjSk-LP7UtpJlFafEuJBR66Q
X-Requested-With: XMLHttpRequest
Content-Length: 0

那么,接下来的请求是这样的:

打字稿

GET http://localhost:5000/machine?id=n2xReT4vy3KPuakBsaSBuA&access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoidGVzdCIsImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvcm9sZSI6IkFkbWluaXN0cmF0b3IiLCJleHAiOjE1NzYxODYwMTYsImlzcyI6Im5TY3J5cHQsIEluYy4iLCJhdWQiOiJuU3R1ZGlvIFVzZXIifQ.qxAzu-NgzlnfCqyysiML4Z0_s6UBTeRb7wcuGno9rk4 HTTP/1.1
Host: localhost:5000
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) nstudio-pro/1.0.0 Chrome/78.0.3905.1 Electron/7.0.0 Safari/537.36
Upgrade: websocket
Origin: http://localhost:3000
Sec-WebSocket-Version: 13
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US
Cookie: .AspNetCore.Identity.Application=CfDJ8Dp0ZrsdLf1KpYnUb_iitp4gyw5TR5uJhBjiI8pFnzSSEM9ALjY6XbRVrMURJ_NrkK9IAD0Xlqy3l6HEQNJkfGG7vwCRfj5x4NjEW4Msm5GoQMuhG6epa3Q3r8QDhdC4z3tJSS0bRZ_EXvmnnnVWYvw4lILddLABWnf3leeMXrKUmq6AUAJPy1SVgj4fJQ8BGOo5HLPZDLZxN-m3ZV0jUaDkOf_mosTAz7JTjI53bAlxD0hi78YYzkVfpa8dEs8gXOTD85f96_m5DGGoMMCnvsjMP6ST1Q87rWHCCsxUPPLaH_A2xc6JpUqvzV-Pur6KtE8oFmcen4jq7h0kL2akXWUvTApZIxY7lFvFx7x4-8andT1DP7T3tRNdWnoRNRotSoQCp4HtS3Cz0GRwcyaKyhuFBjdUFMj1H0FKDYOEJEiarVMX0bElqgTjGGr7ZiOPyTJq1yHmCOraqdbP7YMycTWfC4F1tPXS0v4KxxNo8F2o31MYlhCx_sTIgEjJUHjdh9iugr401GYzazV3reL4M64YAliZ3fynzXf7ZNVqwUg-OvDzXd0nba4E3BVd_hQDwlssaWYq0DAZvrwO56iUwv1y9e-wehaH6OzocmvujVLX_HYG20BsXN6YdLiPPfhqNdkay50AaTuvXF2kq-exJTEnYBuc9U6eTWn9--mEFXKe4VQlTnn97AGvLfnOt_QrhUK4Pc88Z9q3hHNu7MNhfnE
Sec-WebSocket-Key: 7XIeUeqljhqwC4AencqJxg==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

C#

GET http://localhost:5000/machine?id=ZB_6NJvMFDpt0cRhoqiWkw HTTP/1.1
Host: localhost:5000
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoidGVzdCIsImh0dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vd3MvMjAwOC8wNi9pZGVudGl0eS9jbGFpbXMvcm9sZSI6IkFkbWluaXN0cmF0b3IiLCJleHAiOjE1NzYxODYxOTIsImlzcyI6Im5TY3J5cHQsIEluYy4iLCJhdWQiOiJuU3R1ZGlvIFVzZXIifQ.i_m-hnyZfPmFoUSX9VHPjSk-LP7UtpJlFafEuJBR66Q
X-Requested-With: XMLHttpRequest
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: yUAfoo7mcEy8e/j4irLl5w==

服务器上我的 Startup.cs 如下所示:

services.AddAuthentication(jwtAuthScheme)
                .AddJwtBearer(jwtAuthScheme, options =>
                {
                    options.Events = new JwtBearerEvents
                    {
                        OnMessageReceived = context =>
                        {
                            if (context.Request.Query.ContainsKey("access_token"))
                            {
                                context.Token = context.Request.Query["access_token"];
                            }
                            else if (context.Request.Headers.TryGetValue("Authorization", out var value) && value.Count > 0)
                            {
                                context.Token = value[0].Substring("Bearer ".Length);
                            }
                            return Task.CompletedTask;
                        }
                    };
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        LifetimeValidator = (before, expires, token, param) =>
                        {
                            return expires > DateTime.UtcNow;
                        },
                        ValidateAudience = true,
                        ValidAudience = jwtHandler.TokenAudience,
                        ValidateIssuer = true,
                        ValidIssuer = jwtHandler.TokenIssuer,
                        ValidateActor = false,
                        ValidateLifetime = true,
                        IssuerSigningKey = jwtKey,
                        SaveSigninToken = true
                    };
                    options.SaveToken = true;
                    options.Audience = jwtHandler.TokenAudience;
                });
            services.AddAuthorization();

我有什么做错的地方吗?为什么服务器无法从标头中获取令牌以及为什么 C# .NET 客户端不将令牌放入查询字符串中?

c# typescript jwt signalr asp.net-authorization
2个回答
6
投票

我也有同样的问题(与您的情况唯一的区别是我没有使用 JwtBearer,令牌是在集线器一侧自行处理的)。 C# 客户端无法在 SignalR hub(Asp.Net Core 3.1、Linux x64)上进行身份验证,而 Javascript 客户端则可以轻松完成此操作。

C# 客户端:

connection = new HubConnectionBuilder()
    .WithUrl("https://xxx/signalr", options =>
    { 
         options.AccessTokenProvider = () => Task.FromResult("abcdefgh");
    })
    .WithAutomaticReconnect()
    .Build();
 connection.StartAsync();

Javascript 客户端:

 var connection = new signalR.HubConnectionBuilder()
       .withUrl('https://xxx/signalr', { accessTokenFactory: () => 'abcdefgh' })
       .withAutomaticReconnect()
       .build();
 connection.start().then(function () {...

我发现在第一种情况下,集线器一侧的 request.query 如下所示:

wss://xxx/signalr?id=XVJmpqCyqtV76jLp1M9ew
(未传递access_token参数)。

在第二种情况下,它看起来很正常(存在 access_token 参数):

wss://xxx/signalr?id=XVJmpqCyqtV76jLp1M9ew&access_token=abcdefgh

更新

嗯,现在我很清楚了。对于非基于 Web 的客户端(如 C# 客户端),令牌始终通过授权承载标头发送;对于基于 Web 的客户端 (javascript),令牌通过查询字符串发送。

Ricky,就你的情况而言,我认为,你不需要从授权标头中提取令牌并将其手动分配给

context.Token
- 它是自动完成的。因此,您的
OnMessageReceived
C# 处理程序可能如下所示:

options.Events = new JwtBearerEvents
{
    OnMessageReceived = context =>
    {
         if (context.Request.Query.ContainsKey("access_token"))
         {
              context.Token = context.Request.Query["access_token"];
         }
         return Task.CompletedTask;
    }
};

0
投票

已经 2023 年了,.Net 桌面客户端问题仍然存在。 我的解决方法是在自定义标头中传递令牌,以防令牌在查询字符串中不可用。

服务器/集线器端身份验证

OnMessageReceived = context =>
{
    var accessToken = context.Request.Query[SecurityKeys.AccessToken];
    if (string.IsNullOrEmpty(accessToken))
    {
        var authResult = context.Request.Headers.GetCommaSeparatedValues(SecurityKeys.Auth);
        accessToken = authResult[0];
    }

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

    return Task.CompletedTask;
}

.Net客户端/控制台端代码:

var token = "*********";

hubConnection = new HubConnectionBuilder()
        .WithUrl(url, options =>
        {
            options.UseDefaultCredentials = true;
            options.AccessTokenProvider = () => Task.FromResult(token);
            options.Headers.Add("Auth", token);
        })
        .Build();
© www.soinside.com 2019 - 2024. All rights reserved.