【问题标题】:Resource based authorization in SignalRSignalR 中基于资源的授权
【发布时间】:2021-08-31 11:06:32
【问题描述】:

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

例如,这是我的控制器。

[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());
}

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

【问题讨论】:

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


    【解决方案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&lt;ChatParticipantRequirement&gt;AuthorizationHandler&lt;ChatParticipantRequirement, HubInvocationContext&gt;,没有办法绕过它。至于代码复制,您可以从HttpContextHubInvocationContext 获取处理程序中的聊天ID,然后将其传递给您自定义编写的MyAuthorizer,您可以将其注入到两个处理程序中:

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

    【讨论】:

    • 谢谢,这和我想的一样,我不能重复使用我的 [Authorize(Policy = PolicyNames.ChatParticipant)],我必须手动调用我的授权服务。
    • 好吧,您仍然可以重用策略处理程序并留在 .net core 的授权框架中。只是不可能将运行时信息(聊天 ID)传递到属性中,因为它是一个编译时构造。如果您按资源授权,这就是这样做的方法。
    • 是的,我知道,但是在授权处理程序中,我可以从 HttpContext 中的 RouteValues/Query 获取“chatId”,因此每次我想使用授权处理程序时,我都不必从数据库中获取“Chat”对象.
    猜你喜欢
    • 2019-04-15
    • 2013-09-22
    • 2023-03-25
    • 2019-07-18
    • 1970-01-01
    • 2014-09-12
    • 2020-06-18
    • 2015-03-23
    • 1970-01-01
    相关资源
    最近更新 更多