【问题标题】:SignalR Hub with OpenId (AuthenticationTypes.Federation) Returns 401SignalR Hub 与 OpenId (AuthenticationTypes.Federation) 返回 401
【发布时间】:2021-11-07 18:07:37
【问题描述】:

我有一个使用 OpenId 进行身份验证的应用程序。启动中的代码是:

services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
    options.LoginPath = new PathString("/account/login");
})
.AddOpenIdConnect("CustomScheme", options =>
{
    // options configured
});

我有一个集线器,并在app.UseEndpoints 中连接集线器,如下所示:

endpoints.MapHub<SpecialMessageHub>("myspecialhub");

除了发布我的整个 Startup.cs 文件之外,这里是一个快速概述:

  1. app.UseAuthentication();app.UseAuthorization();app.UseEndpoints 之前
  2. services.AddSignalR();services.AddServerSideBlazor();services.AddAuthenticationservices.AddAuthorization 之后

我已经为这个问题简化了我的 Hub:

using Example.Extensions;
using Example.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using System.Threading;
using System.Threading.Tasks;

namespace Example.Hubs
{
    [Authorize]
    public class SpecialMessageHub : Hub
    {
        public override Task OnConnectedAsync()
        {
            var customerId = Context.User.GetCustomerId();
            Groups.AddToGroupAsync(Context.ConnectionId, GetCustomerGroupName(customerId));

            return base.OnConnectedAsync();
        }

        public Task SendMessage(string customerId,
            SpecialMessage message,
            CancellationToken cancellationToken)
        {
            return Clients.Groups(GetCustomerGroupName(customerId)).SendAsync("ReceiveMessage", message, cancellationToken);
        }

        public static string GetCustomerGroupName(string customerId) => $"customers-{customerId}";
    }
}

**注意:GetCustomerId 是我项目中的扩展方法

我有一个简单的组件来测试这个:

@page "/"
@using Microsoft.AspNetCore.SignalR.Client
@using Example.Models
@inject Microsoft.AspNetCore.Components.NavigationManager NavigationManager
@implements IAsyncDisposable

<h2>@myMessage.Id</h2>
<div>@myMessage.Message</div>

@code {
    private HubConnection hubConnection;
    private SpecialMessage myMessage = new SpecialMessage();

    protected override async Task OnInitializedAsync()
    {
        if (!IsConnected)
        {
            hubConnection = new HubConnectionBuilder()
                .WithUrl(NavigationManager.ToAbsoluteUri("/myspecialhub"))
                .WithAutomaticReconnect()
                .Build();

            hubConnection.On<string, SpecialMessage>
                ("ReceiveMessage", (user, message) =>
                {
                    myMessage = message;
                    StateHasChanged();
                });

            await hubConnection.StartAsync().ConfigureAwait(false);
        }
    }

    public bool IsConnected => hubConnection != null
        && hubConnection.State == HubConnectionState.Connected;

    public async ValueTask DisposeAsync()
    {
        await hubConnection?.DisposeAsync();
    }
}

然后我使用以下内容将该组件添加到我的视图中:

<component type="typeof(SpecialMessageComponent)" render-mode="ServerPrerendered"/>

当我点击这个视图时,它返回一个 401:

基于这个https://docs.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-3.1,cookie应该被传递到集线器:

在基于浏览器的应用程序中,cookie 身份验证允许您现有的 用户凭据自动流向 SignalR 连接。什么时候 使用浏览器客户端,无需额外配置。如果 用户登录到您的应用,SignalR 连接 自动继承此身份验证。

我能够验证用户是否已通过身份验证,并且在控制器和组件中都有我正在寻找的声明。我觉得这应该相当简单,不需要多次跳跃,因此非常感谢任何帮助。

以下是我尝试过的一些事情:

  1. 从视图中的上下文中拉出“access_token”和“id_token”,并将其作为参数传递给组件。然后基于这里https://docs.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-3.1#bearer-token-authentication,我做了这样的事情,但这仍然导致了401:

     [Parameter]
     public string AccessToken { get; set; }
    
     protected override async Task OnInitializedAsync()
     {
         if (!IsConnected)
         {
             hubConnection = new HubConnectionBuilder()
                 .WithUrl(NavigationManager.ToAbsoluteUri("/myspecialhub"), options =>
                 {
                     options.AccessTokenProvider = () => Task.FromResult(AccessToken);
                 })
                 .WithAutomaticReconnect()
                 .Build();
    
                 hubConnection.On<string, SpecialMessage>
                     ("ReceiveMessage", (user, message) =>
                     {
                         myMessage = message;
                         StateHasChanged();
                     });
    
                 await hubConnection.StartAsync().ConfigureAwait(false);
             }
         }
     }
    
  2. 我试图通过将ClaimsPrincipal 替换为新的IIdentity 并将AuthenticationType 设置为CookieAuthenticationDefaults.AuthenticationScheme 来操纵用户。这导致了一个令人讨厌的无限循环,但在重新启动应用程序后,身份被交换,但是,我仍然收到 401。

  3. 我也尝试过其他选项,但我似乎无法让它与 OpenId 一起使用。

【问题讨论】:

    标签: c# asp.net-core signalr signalr-hub


    【解决方案1】:

    总而言之,Blazor 组件是在服务器上运行的,因此所进行的 HubConnection 是从服务器到服务器。

    await hubConnection.StartAsync().ConfigureAwait(false);
    

    这会建立服务器到服务器的连接,这就是为什么 HubCallerContext.User 不是登录到 Web 应用程序的用户。

    就我个人而言,这对我来说并不直观。因为这是在服务器上运行的,所以没有与浏览器建立套接字。为了实现这一点,必须使用 Blazor WebAssembly 在客户端浏览器上运行代码。我已经退回到使用 JavaScript 来处理这个问题。

    这是微软回答为什么这不起作用的问题:https://github.com/dotnet/aspnetcore/issues/36421

    【讨论】:

      猜你喜欢
      • 2019-06-20
      • 1970-01-01
      • 2020-01-01
      • 1970-01-01
      • 2021-03-26
      • 2019-12-02
      • 1970-01-01
      • 1970-01-01
      • 2020-08-23
      相关资源
      最近更新 更多