【问题标题】:How to implement async IUserIdProvider for SignalR如何为 SignalR 实现异步 IUserIdProvider
【发布时间】:2021-09-20 17:37:14
【问题描述】:
我有一个身份验证设置,我在声明中存储了一个会话 ID 和一个未经验证的用户 ID。然后,在普通控制器中,我会在我的数据库中查找会话以验证它是否与用户 ID 匹配并考虑用户已登录。
我正在尝试使用 Azure SignalR。我希望能够通过 userID 向连接的用户发送消息,并且我需要实现 IUserIdProvider。它上面的 GetUserId 方法不是异步的,但我需要做的是执行相同的逻辑,在它认为用户有效之前,它会针对数据库验证声明中的会话 ID 和用户 ID。此代码将是异步的,但 GetUserId 方法不是异步的。
我有什么选择?
谢谢!
【问题讨论】:
标签:
c#
.net-core
signalr
azure-signalr
【解决方案1】:
我相信IUserIdProvider.GetUserId(HubConnectionContext) 不是异步背后的原因是对数据库或其他外部资源的调用会在请求管道的早期发生。这可能包括将外部用户 ID 或会话 ID 映射到内部用户 ID 等操作。
我解决类似问题的方法是将我的数据库查找放在 IClaimsTransformation 实现中,该实现存储了我在流经请求的用户的声明中查找的值。
public static class ApplicationClaimTypes
{
public static string UserId => "user-id";
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Identity.Web;
class MsalClaimsTransformation : IClaimsTransformation
{
// IMapExternalUsers represents the actions you must take to map an external id to an internal user id
private readonly IMapExternalUsers _externalUsers;
private ClaimsPrincipal _claimsPrincipal;
public MsalClaimsTransformation(IMapExternalUsers externalUsers)
{
_externalUsers = externalUsers;
}
public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal claimsPrincipal)
{
_claimsPrincipal = claimsPrincipal;
// This check is important because the IClaimsTransformation may run multiple times in a single request
if (!claimsPrincipal.HasClaim(claim => claim.Type == ApplicationClaimTypes.UserId))
{
var claimsIdentity = await MapClaims();
claimsPrincipal.AddIdentity(claimsIdentity);
}
return claimsPrincipal;
}
private async Task<ClaimsIdentity> MapClaims()
{
var externalIds = new[]
{
// Extensions from Microsoft.Identity.Web
_claimsPrincipal.GetHomeObjectId(),
_claimsPrincipal.GetObjectId()
}
.Distinct()
.Where(id => !string.IsNullOrWhiteSpace(id))
.ToArray();
// Replace with implementation specific to your use case for mapping session/external
// id to authenticated internal user id.
var userId = await _externalUsers.MapExternalIdAsync(externalIds);
var claimsIdentity = new ClaimsIdentity();
AddUserIdClaim(claimsIdentity, userId);
// Add other claims as needed
return claimsIdentity;
}
private void AddUserIdClaim(ClaimsIdentity claimsIdentity, Guid? userId)
{
claimsIdentity.AddClaim(new Claim(ApplicationClaimTypes.UserId, userId.ToString()));
}
}
一旦您将内部用户 ID 作为声明存储在 User 对象中,SignalR 的 GetUserId() 方法就可以访问它,而无需异步代码。
using System.Security.Claims;
using Microsoft.AspNetCore.SignalR;
internal class SignalrUserIdProvider : IUserIdProvider
{
public string GetUserId(HubConnectionContext connection)
{
var httpContext = connection.GetHttpContext();
// If you have a multi-tenant application, you may have an
// extension method like this to get the current tenant the user
// is in. Otherwise just remove it.
var tenantIdentifier = httpContext.GetTenantIdentifier();
var userId = connection.User.FindFirstValue(ApplicationClaimTypes.UserId);
if (string.IsNullOrWhiteSpace(userId))
{
return string.Empty;
}
return $"{tenantIdentifier}-{userId}";
}
}