SignalR 中基于资源的授权

时间:2021-06-15 15:11:02

标签: asp.net-core .net-core signalr azure-signalr

我有带有自定义策略和授权处理程序的 Web API。 我想重用授权处理程序,但在信号器的集线器上使用属性时,HttpContext 为 null。

例如这是我的控制器。

[Authorize]
public sealed class ChatsController : ControllerBase
{
    [HttpPost("{chatId}/messages/send")]
    [Authorize(Policy = PolicyNames.ChatParticipant)]
    public Task SendMessage() => Task.CompletedTask;
}

这是我的授权处理程序。我可以从 HttpContext 中提取“chatId”,然后使用我的自定义逻辑来授权用户。

internal sealed class ChatParticipantRequirementHandler : AuthorizationHandler<ChatParticipantRequirement>
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public ChatParticipantRequirementHandler(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ChatParticipantRequirement requirement)
    {
        if(_httpContextAccessor.HttpContext != null)
        {
            // Logic
        }

        return Task.CompletedTask;
    }
}

但是,这不适用于 Azure SignalR,因为我无权访问 HttpContext。我知道我可以提供自定义 IUserIdProvider,但我不知道如何从我的自定义授权处理程序中的“Join”方法访问“chatId”。

[Authorize]
public sealed class ChatHub : Hub<IChatClient>
{
    [Authorize(Policy = PolicyNames.ChatParticipant)]
    public async Task Join(Guid chatId)
    {
        await Groups.AddToGroupAsync(Context.ConnectionId, chatId.ToString());
}

是否可以重复使用我的授权处理程序? 我想避免复制粘贴我的代码。 一种解决方案是提取我的授权代码以分离服务,但随后我必须从我的集线器手动调用这些服务并放弃 [授权] 方式。

1 个答案:

答案 0 :(得分:1)

您的聊天是一种资源,您想使用基于资源的授权。在这种情况下,具有属性的声明性授权是不够的,因为聊天 ID 仅在运行时已知。因此,您必须将 imperative authorizationIAuthorizationService 一起使用。

现在在您的中心:

[Authorize]
public sealed class ChatHub : Hub<IChatClient>
{
    private readonly IAuthorizationService authService;

    public ChatHub(IAuthorizationService authService)
    {
        this.authService = authService;
    }

    public async Task Join(Guid chatId)
    {
        // Get claims principal from authorized hub context
        var user = this.Context.User;

        // Get chat from DB or wherever you store it, or optionally just pass the ID to the authorization service
        var chat = myDb.GetChatById(chatId);

        var validationResult = await this.authService.AuthorizeAsync(user, chat, PolicyNames.ChatParticipant);

        if (validationResult.Succeeded)
        {
            await Groups.AddToGroupAsync(Context.ConnectionId, chatId.ToString());
        }
    }
}

您的授权处理程序应该看起来不同,因为它需要签名中的聊天资源来进行这种评估:

internal sealed class ChatParticipantRequirementHandler : AuthorizationHandler<ChatParticipantRequirement, Chat>
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public ChatParticipantRequirementHandler(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ChatParticipantRequirement requirement, Chat chat)
    {
        // You have both user and chat now
        var user = context.User;
        if (this.IsMyUserAuthorizedToUseThisChat(user, chat))
        {
            context.Succeed(requirement);
        }
        else
        {
            context.Fail();
        }

        return Task.CompletedTask;
    }
}

编辑:实际上还有另一个我不知道的选项

您可以使用 SignalR Hub 为授权方法提供的 HubInvocationContext。这可以自动注入到你的 AuthorizationHandler 中,它应该是这样的:

public class ChatParticipantRequirementHandler : AuthorizationHandler<ChatParticipantRequirement, HubInvocationContext>
    {
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ChatParticipantRequirement requirement, HubInvocationContext hubContext)
        {
            var chatId = Guid.Parse((string)hubContext.HubMethodArguments[0]);
        }
    }

Hub 方法通常会用 [Authorize(Policy = PolicyNames.ChatParticipant)] 修饰

您仍然有两个授权处理程序,AuthorizationHandler<ChatParticipantRequirement>AuthorizationHandler<ChatParticipantRequirement, HubInvocationContext>,无法绕过它。至于代码复制,您可以从 HttpContextHubInvocationContext 获取处理程序中的聊天 ID,然后将其传递给您可以注入两者的自定义编写的 MyAuthorizer处理程序:

public class MyAuthorizer : IMyAuthorizer 
{
  public bool CanUserChat(Guid userId, Guid chatId);
}