ASP.NET 核心 Angular - 根据登录用户发送不同的 SignalR 消息

ASP.NET Core Angular - send different SignalR messages based on logged in user

我有 Angular SPA ASP.NET 带有身份的核心应用程序 (IdentityServer4)。我使用 SignalR 向客户端推送实时消息。

但是我必须“广播”消息。无论他们需要什么,所有客户端都会收到相同的消息,然后他们会在 Typescript 中弄清楚——他们是否需要这条消息。

我想要的是能够决定哪个 SignalR 客户端应该接收消息以及什么内容 - 这将使消息更短并完全减少客户端的处理时间。

我看到有 hub.Client.User(userId) 方法 - 这就是我需要的。但是,SignalR 似乎不知道身份用户 ID。

如果我覆盖 public override Task OnConnectedAsync() - 内部上下文没有任何有用的信息,例如 user/principals/claims - 是空的。

如何找出哪个 IdentityServer4 用户正在连接到集线器?

EDIT1 建议实施 IUserIdProvider 不起作用 - 所有 xs 均为空。

https://docs.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-5.0#use-claims-to-customize-identity-handling

public string GetUserId(HubConnectionContext connection)
{
    var x1 = connection.User?.FindFirst(ClaimTypes.Email)?.Value;
    var x2 = connection.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
    var x3 = connection.User?.FindFirst(ClaimTypes.Name)?.Value;
...

EDIT2https://docs.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-5.0 实施了“身份服务器 JWT 身份验证” - 也不起作用 - accessTokenPostConfigure[=20 中为空=]

您需要实施 IUserIdProvider 并将其注册到服务集合中。

检查这个问题 -

据了解,SignalR 服务器上下文中的用户数据为空,因为授权未按我的预期工作。 使用 Identity Server 实现 SignalR 授权似乎是一件大事,并且存在安全风险,因为它会影响整个应用程序 - 您基本上需要手动覆盖 Identity Server 已经完成的大量代码,只是为了满足 SignalR case.

所以我想出了一个 解决方法,请在此处查看我对自己的回答:

编辑:我错过了一个明显的解决方案 - 请参阅其他答案。不过,这仍然是有效的解决方法,所以我打算让它挂在这里。

有一个明显的解决方案。这是在创建具有身份的 asp.net 核心 angular 应用程序后可以使用的示例。

请注意,在这个特定场景中(Angular with ASP.NET Core with Identity)你 NOT 需要实现任何其他,与人们误读文档的多项建议相反:https://docs.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-5.0

客户端:

import { AuthorizeService } from '../../api-authorization/authorize.service';

. . .

constructor(. . . , authsrv: AuthorizeService) {

  this.hub = new HubConnectionBuilder()
    .withUrl("/newshub", { accessTokenFactory: () => authsrv.getAccessToken().toPromise() })
    .build();

服务器端:

[Authorize]
public class NewsHub : Hub
{
    public static readonly SortedDictionary<string, HubAuthItem> Connected = new SortedDictionary<string, HubAuthItem>();

    public override Task OnConnectedAsync()
    {
        NewsHub.Connected.Add(Context.ConnectionId, new HubAuthItem
        {
            ConnectionId = Context.ConnectionId,
            UserId = Context.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value
        });

        return base.OnConnectedAsync();
    }
}

这样使用:

if(NewsHub.Connected.Count != 0)
    foreach (var cnn in NewsHub.Connected.Values.Where(i => !string.IsNullOrEmpty(i.UserId)))
        if(CanSendMessage(cnn.UserId)
            hub.Clients.Users(cnn.UserId).SendAsync("servermessage", "message text");