Blazor WebAssembly SignalR 身份验证

Posted

技术标签:

【中文标题】Blazor WebAssembly SignalR 身份验证【英文标题】:Blazor WebAssembly SignalR Authentication 【发布时间】:2020-09-16 08:28:49 【问题描述】:

我希望看到有关如何使用 Blazor 的 WebAssembly 风格向 SignalR 集线器连接添加身份验证的示例。我的 dotnet 版本是 3.1.300。

我可以按照以下步骤使开放的、未经身份验证的 SignalR 连接正常工作:https://docs.microsoft.com/en-us/aspnet/core/tutorials/signalr-blazor-webassembly?view=aspnetcore-3.1&tabs=visual-studio

我发现的所有教程似乎都较旧或针对服务器托管类型,并且不使用内置模板。

我已使用适当的模板和这些说明(包括数据库)向后端的其余部分添加了身份验证: https://docs.microsoft.com/en-us/aspnet/core/security/blazor/?view=aspnetcore-3.1

但每次我将 [Authenticate] 添加到聊天中心时,都会返回一个错误。有什么方法可以扩展第一个教程,我们可以验证那里创建的集线器?搭上内置的 ASP.NET 系统会很棒,但是如果最好的话,我只需将令牌作为附加参数传入并自己做就可以了。在这种情况下,我需要学习如何从 Blazor WebAssembly 中获取令牌,然后在服务器上的某个位置查找它。这似乎是错误的,但作为替代方案,它基本上可以满足我的需求。

那里有各种各样的半解决方案,或者是为旧版本设计的,但没有什么可以建立在 MS 提供的库存教程之上。

更新: 按照此新闻稿https://devblogs.microsoft.com/aspnet/blazor-webassembly-3-2-0-preview-2-release-now-available/ 中的提示,我现在可以从剃刀页面内部获取一个令牌,并将其注入到标题中。我想这很好??但是那我如何获取它并在服务器上使用它呢?

这是剃须刀代码的sn-p:

protected override async Task OnInitializedAsync()

    var httpClient = new HttpClient();
    httpClient.BaseAddress = new Uri(UriHelper.BaseUri);

    var tokenResult = await AuthenticationService.RequestAccessToken();

    if (tokenResult.TryGetToken(out var token))
    
        httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer token.Value");

        hubConnection = new HubConnectionBuilder()
            .WithUrl(UriHelper.ToAbsoluteUri("/chatHub"), options =>
            
                options.AccessTokenProvider = () => Task.FromResult(token.Value);
            )
            .Build();
    

更新 2: 我在这里尝试了提示:https://github.com/dotnet/aspnetcore/issues/18697

并将我的代码更改为:

        hubConnection = new HubConnectionBuilder()
            .WithUrl(NavigationManager.ToAbsoluteUri("/chatHub?access_token=" + token.Value))
            .Build();

但没有快乐。

【问题讨论】:

你看过docs.microsoft.com/en-us/aspnet/core/signalr/…吗? 我看过了,它很有用,但最终我没有成功。我似乎已经正确地抓住了客户端中的令牌。我可以打印出来,然后出现一个大长字符串。我可以将它添加到 options.AccessTokenProvider,但服务器似乎仍然配置错误。我在启动时在 app.UseIdentityServer() 中得到一个空引用异常。我的意思是,这当然是我的全部,但如果扩展示例,即使是像我这样的菜鸟也可以得到它。 【参考方案1】:

我也遇到过同样的问题。

我的解决方案是双向的:我必须在前端和后端修复一些东西。

布雷泽

在您的连接构建器中,您应该添加 AccessTokenProvider:

string accessToken = "eyYourToken";
connection = new HubConnectionBuilder()
    .WithUrl("https://localhost:5001/hub/chat", options =>
    
        options.AccessTokenProvider = () => Task.FromResult(token.Value);
    )
    .Build();

options.AccessTokenProvider 的类型为Func<Task<string>>,因此您也可以在此处执行异步操作。如果需要的话。

仅这样做,应该允许 SignalR 工作。

后端

但是!当 SignalR 尝试创建 WebSocket 连接时,您可能仍会看到错误。这是因为您可能在后端使用 IdentityServer,而这不支持来自查询字符串的 Jwt 令牌。不幸的是,SignalR 试图通过一个名为 access_token 的查询字符串参数来授权 websocket 请求。

将此代码添加到您的启动:

.AddJwtBearer("Bearer", options =>

    // other configurations omitted for brevity
    options.Events = new JwtBearerEvents
    
        OnMessageReceived = context =>
        
            var accessToken = context.Request.Query["access_token"];

            // If the request is for our hub...
            var path = context.HttpContext.Request.Path;
            if (!string.IsNullOrEmpty(accessToken) &&
                (path.StartsWithSegments("/hubs"))) // Ensure that this path is the same as yours!
            
                // Read the token out of the query string
                context.Token = accessToken;
            
            return Task.CompletedTask;
        
    ;
);

编辑 1:阐明 Blazor SignalR 代码的用法

【讨论】:

access_token 是如何设置的? Sander 是正确的,这里是官方文档docs.microsoft.com/en-us/aspnet/core/signalr/…【参考方案2】:

就我而言(Blazor WebAssembly,使用 JWT Bearer Token Auth 托管在 ASP.NET Core 5.0 上),我必须添加以下内容:

Blazor WASM 客户端

在建立连接时(在我的例子中:在某些服务代理类的构造函数中),使用IAccessTokenProvider 并像这样配置AccessTokenProvider 选项:

public ServiceProxy(HttpClient httpClient, IAccessTokenProvider tokenProvider) 
    HubConnection = new HubConnectionBuilder()
        .WithUrl(
            new Uri(httpClient.BaseAddress, "/hubs/service"),
            options => 
                options.AccessTokenProvider = async () => 
                    var result = await tokenProvider.RequestAccessToken();
                    if (result.TryGetToken(out var token)) 
                        return token.Value;
                    
                    else 
                        return string.Empty;
                    
                ;
            )
        .WithAutomaticReconnect() // optional
        .Build();

ASP.NET Core 服务器

将以下内容添加到Startup.ConfigureServices

services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options => 
    // store user's "name" claim in User.Identity.Name
    options.TokenValidationParameters.NameClaimType = "name";

    // pass JWT bearer token to SignalR connection context
    // (from https://docs.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-5.0)
    options.Events = new JwtBearerEvents 
        OnMessageReceived = context => 
            var accessToken = context.Request.Query["access_token"];
            // If the request is for on of our SignalR hubs ...
            if (!string.IsNullOrEmpty(accessToken) &&
                (context.HttpContext.Request.Path.StartsWithSegments("/hubs/service"))) 
                 // Read the token out of the query string
                 context.Token = accessToken;
            
            return Task.CompletedTask;
        
    ;
);

【讨论】:

【参考方案3】:

这是我的解决方案并且有效

[Inject] HttpClient httpClient  get; set; 
[Inject] IAccessTokenProvider tokenProvider  get; set; 
HubConnection hubConnection  get; set; 

(...)

private async Task ConnectToNotificationHub()

    string url = httpClient.BaseAddress.ToString() + "notificationhub";

    var tokenResult = await tokenProvider.RequestAccessToken();

    if (tokenResult.TryGetToken(out var token))
    
        hubConnection = new HubConnectionBuilder().WithUrl(url, options =>
        
            options.Headers.Add("Authorization", $"Bearer token.Value");
        ).Build();


        await hubConnection.StartAsync();

        hubConnection.Closed += async (s) =>
        
            await hubConnection.StartAsync();
        ;

        hubConnection.On<string>("notification", m =>
        
            string msg = m;
        );
    

【讨论】:

以上是关于Blazor WebAssembly SignalR 身份验证的主要内容,如果未能解决你的问题,请参考以下文章

明晚7点半 | Blazor + WebAssembly开启Web开发新体验

Blazor WebAssembly + Grpc Web = 未来?

Blazor WebAssembly 3.2 正式发布

「译」 用 Blazor WebAssembly 实现微前端

一起学Blazor WebAssembly 开发

通过 Serverless 加速 Blazor WebAssembly