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



我正在尝试通过 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==

服务器上的"我的启动.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 客户端不将令牌放在查询字符串中?

我有同样的问题(与您的情况的唯一区别是我没有使用 JwtBearer,令牌是在集线器一侧自行处理的(。C#客户端无法在SignalR中心(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- 它是自动完成的。因此,OnMessageReceivedC# 处理程序可能如下所示:

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

最新更新