我有带有自定义策略和授权处理程序的 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());
}
是否可以重复使用我的授权处理程序? 我想避免复制粘贴我的代码。 一种解决方案是提取我的授权代码以分离服务,但随后我必须从我的集线器手动调用这些服务并放弃 [授权] 方式。
答案 0 :(得分:1)
您的聊天是一种资源,您想使用基于资源的授权。在这种情况下,具有属性的声明性授权是不够的,因为聊天 ID 仅在运行时已知。因此,您必须将 imperative authorization 与 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());
}
}
}
您的授权处理程序应该看起来不同,因为它需要签名中的聊天资源来进行这种评估:
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>
,无法绕过它。至于代码复制,您可以从 HttpContext
或 HubInvocationContext
获取处理程序中的聊天 ID,然后将其传递给您可以注入两者的自定义编写的 MyAuthorizer
处理程序:
public class MyAuthorizer : IMyAuthorizer
{
public bool CanUserChat(Guid userId, Guid chatId);
}