SignalR 中基于资源的授权

Deg*_*sto 6 signalr .net-core asp.net-core azure-signalr

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

例如,这是我的控制器。

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

这是我的授权处理程序。我可以从 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,这不适用于 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());
}
Run Code Online (Sandbox Code Playgroud)

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

Max*_*ikh 1

您的聊天是一种资源,您希望使用基于资源的授权。在这种情况下,使用属性进行声明性授权是不够的,因为聊天 ID 仅在运行时已知。因此,您必须使用命令式授权IAuthorizationService

现在在您的中心:

[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());
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

您可以利用HubInvocationContextSignalR Hub 提供的授权方法。这可以自动注入到您的 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]);
        }
    }
Run Code Online (Sandbox Code Playgroud)

Hub 方法通常会被装饰为[Authorize(Policy = PolicyNames.ChatParticipant)]

您仍然会有两个授权处理程序,AuthorizationHandler<ChatParticipantRequirement>并且AuthorizationHandler<ChatParticipantRequirement, HubInvocationContext>没有办法解决它。至于代码复制,您可以从HttpContext或获取处理程序中的聊天 ID HubInvocationContext,然后将其传递给您MyAuthorizer可以注入到两个处理程序中的自定义编写的内容:

public class MyAuthorizer : IMyAuthorizer 
{
  public bool CanUserChat(Guid userId, Guid chatId);
}
Run Code Online (Sandbox Code Playgroud)