ASP.NET Core SignalR AccessTokenProvider未在每次请求执行,如何更新令牌?
我有一个ASP.NET Core(.NET 7)SignalR测试Hub:
public class TestHub : Hub { public async Task<string> GetToken() { return await Task.FromResult("testtoken"); } [Authorize] public async Task<string> Echo(string message) { return await Task.FromResult($"Echoing {message}"); } }
需求是:SignalR客户端先通过无需授权的GetToken调用获取令牌,之后在后续调用(如Echo)中使用该令牌(自定义认证方案)进行授权。
但按以下方式创建客户端连接时无法实现需求:
string? token = null; HubConnection connection = new HubConnectionBuilder() .WithUrl("http://localhost:5100/test", options => { options.AccessTokenProvider = () => Task.FromResult(token); }) .Build(); await connection.StartAsync();
调试发现,AccessTokenProvider的回调仅在连接初始化时执行,此时token仍为null,导致后续调用无法传递令牌。原本预期该函数会像gRPC的类似功能那样在每次请求时运行。
这导致客户端初始调用成功,但第二次调用失败:
token = await connection.InvokeAsync<string>("GetToken"); // 正常 string result = await connection.InvokeAsync<string>("Echo", "Hello?"); // 失败
请问在单个SignalR连接中更新令牌的预期方式是什么?
- 是否可以重新执行
AccessTokenProvider设置的函数? - 是否可以在客户端手动设置令牌?
- 还是必须创建单独的Hub并使用两个连接,一个仅用于获取令牌,另一个从启动时就需要令牌?
(最终我还需要在C++ SignalR客户端中实现此功能,若有解决方案且了解如何适配C++客户端,相关信息也非常欢迎。)
1. 让AccessTokenProvider动态返回最新令牌
不需要重新设置AccessTokenProvider,只要让它返回当前最新的令牌值即可。修改客户端代码,让回调直接引用可更新的变量:
string? token = null; HubConnection connection = new HubConnectionBuilder() .WithUrl("http://localhost:5100/test", options => { // 每次需要令牌时都会执行此回调,返回当前最新的token值 options.AccessTokenProvider = () => Task.FromResult(token); }) .Build(); await connection.StartAsync(); // 获取令牌后更新变量 token = await connection.InvokeAsync<string>("GetToken"); // 此时调用Echo时,AccessTokenProvider会自动返回最新token string result = await connection.InvokeAsync<string>("Echo", "Hello?"); // 执行成功
注:SignalR的
AccessTokenProvider在每次发送需要授权的请求前都会执行回调,之前的问题是初始化时token为null,但更新变量后,后续请求会重新执行回调拿到新值。
2. 手动触发连接协商(可选)
如果需要主动让新令牌立即生效(比如令牌过期刷新场景),可以在更新令牌后调用NegotiateAsync()重新协商连接:
token = "new-refreshed-token"; await connection.NegotiateAsync();
3. C++客户端适配
对于基于cpprestsdk的官方C++ SignalR客户端,可通过http_client_config设置动态令牌提供者:
std::string token; web::http::client::http_client_config config; config.set_access_token_provider([&token]() { return pplx::task_from_result(token); }); auto connection = signalr::hub_connection_builder::create("http://localhost:5100/test") .with_http_client_config(config) .build(); connection.start().wait(); // 获取令牌并更新变量 token = connection.invoke<std::string>("GetToken").get(); // 后续调用Echo时自动使用最新令牌 auto result = connection.invoke<std::string>("Echo", "Hello?").get();
若需手动触发令牌生效,同样调用connection.negotiate().wait()重新协商即可。
是否需要两个连接?
不需要创建两个连接,单个连接完全可以满足需求。只要确保AccessTokenProvider的回调能返回最新令牌值,SignalR会自动在每次授权请求中使用该值。
内容的提问来源于stack exchange,提问作者Ray

