#asp.net-core #.net-core #signalr #azure-signalr
Вопрос:
У меня есть веб-API с пользовательскими политиками и обработчиками авторизации. Я хотел повторно использовать обработчики авторизации, но HttpContext равен нулю, когда атрибут используется в концентраторе signalr.
Например, это мой контроллер.
[Authorize]
public sealed class ChatsController : ControllerBase
{
[HttpPost("{chatId}/messages/send")]
[Authorize(Policy = PolicyNames.ChatParticipant)]
public Task SendMessage() => Task.CompletedTask;
}
И это мой обработчик авторизации. Я могу извлечь «chatId» из HttpContext, а затем использовать свою пользовательскую логику для авторизации пользователя.
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. Я знаю, что могу предоставить пользовательский идентификатор пользователя, но я понятия не имею, как получить доступ к «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:
Ваш чат-это ресурс, и вы хотите использовать авторизацию на основе ресурсов. В этом случае декларативной авторизации с атрибутом недостаточно, поскольку идентификатор чата известен только во время выполнения. Таким образом, вы должны использовать императивную авторизацию 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;
}
}
Правка: на самом деле есть еще один вариант, о котором я не знал
Вы можете использовать HubInvocationContext
тот концентратор SignalR, который предоставляет авторизованные методы. Это может быть автоматически введено в ваш обработчик авторизации, который должен выглядеть следующим образом:
public class ChatParticipantRequirementHandler : AuthorizationHandler<ChatParticipantRequirement, HubInvocationContext>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ChatParticipantRequirement requirement, HubInvocationContext hubContext)
{
var chatId = Guid.Parse((string)hubContext.HubMethodArguments[0]);
}
}
Метод ступицы будет обычно украшен [Authorize(Policy = PolicyNames.ChatParticipant)]
У вас все равно будет два обработчика авторизации, AuthorizationHandler<ChatParticipantRequirement>
и AuthorizationHandler<ChatParticipantRequirement, HubInvocationContext>
обойти это невозможно. Что касается дублирования кода, вы, однако , можете просто получить идентификатор чата в обработчике либо из HttpContext
или HubInvocationContext
, а затем передать его вам, написанному на заказ MyAuthorizer
, который вы могли бы ввести в оба обработчика:
public class MyAuthorizer : IMyAuthorizer
{
public bool CanUserChat(Guid userId, Guid chatId);
}
Комментарии:
1. Спасибо, все так, как я и думал, я не могу повторно использовать свою [Авторизацию(Политика = Имена политик. Участник чата)] и я должен вручную вызвать свою службу авторизации.
2. Что ж, вы все равно можете повторно использовать обработчик политики и оставаться в рамках авторизации .net core. Просто невозможно передать информацию о времени выполнения (идентификатор чата) в атрибут, потому что это конструкция времени компиляции. Это можно сделать, если вы авторизуетесь на каждом ресурсе.
3. Да, я знаю, но в обработчике авторизации я мог получить «Идентификатор чата» из значений/запроса в HttpContext, поэтому мне не нужно было получать объект «Чат» из базы данных каждый раз, когда я хотел использовать свой обработчик авторизации.