Перейти к содержанию

Применяем авторизацию в SignalR

Аутентификация сама по себе — это хорошо, но далеко не всегда эффективно. Необходимо быть уверенным не только в том, что доступ к системе имеют известные пользователи, но и в том, что пользователям доступны только те ресурсы, к которым им разрешено иметь доступ. Именно за эту часть отвечает авторизация.
В ASP.NET Core cуществует1 несколько различных типов авторизации, и все они применимы к SignalR. Необходимо только сконфигурировать авторизационные обработчики. Существует множество путей сделать это, и мы рассмотрим некоторые из них.

Создание особого требования

Одним из способов является реализация2 особого класса, унаследованного от AuthorizationHandler. Создадим в проекте SignalRServer класс RoleRequirement.cs со следующим содержимым:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using System.Security.Claims;

namespace SignalRServer
{
    public class RoleRequirement: AuthorizationHandler<RoleRequirement, HubInvocationContext>,
        IAuthorizationRequirement
    {
        private readonly string requiredRole;
        public RoleRequirement(string requiredRole)
        {
            this.requiredRole = requiredRole;
        }

        protected override Task HandleRequirementAsync(
            AuthorizationHandlerContext context,
            RoleRequirement requirement,
            HubInvocationContext resource)
        {
            var roles = ((ClaimsIdentity)context.User.Identity).Claims
                .Where(c => c.Type == ClaimTypes.Role)
                .Select(c => c.Value);

            if(roles.Contains(requiredRole))
                context.Succeed(requirement);

            return Task.CompletedTask;
        }
    }
}

Когда этот класс будет зарегистрирован в промежуточном ПО авторизации, метод HandleRequirementAsync будет вызван для запроса, из которого уже был выделен аутентификационный токен. В данном случае мы проверяем, что требуемая роль присутствует среди клеймов этого токена.
Видно, что наш класс использует HubInvocationContext в качестве одного из параметров типа. В нашем коде он не используется, но показывает, что это требование будет применено в контексте запроса к хабу SignalR. При этом это не просто маркер — можно реализовать некоторую логику, использующую параметр resource этого типа.

Конфигурируем промежуточное ПО авторизации

Добавляем авторизацию к сервисам:

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("BasicAuth", policy => policy.RequireAuthenticatedUser());
    options.AddPolicy("AdminClaim", policy => policy.RequireClaim("admin"));
    options.AddPolicy("AdminOnly", policy => policy.Requirements.Add(new RoleRequirement("admin")));
})

В этом коде показаны различные способы добавления политик авторизации3.
Первая политика, "BasicAuth", требует, чтобы пользователи были аутентифицированы. Практически то же самое, что и использование атрибута [Authorize] без указания политики.
Вторая политика, "AdminClaim", требует, чтобы у пользователя был клейм admin, при этом неважно, какое значение будет у этого клейма. Также у метода RequireClaim есть перегрузка с двумя параметрами, второй параметр представляет собой массив допустимых значений клейма.
Наконец, третья политика, "AdminOnly" — это политика, где мы применяем наше особое требование. В данном случае, требование выполнится, если у пользователя есть клейм role и его значение равно admin.

Применение правил авторизации к конкретным конечным точкам

Начнем с того, что изменим атрибут, который мы применили к классу LearningHub.

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme + "," + CookieAuthenticationDefaults.AuthenticationScheme, Policy = "BasicAuth")]

Таким образом, политику можно применить к любой конечной точке в ASP.NET Core. Что касается поведения, то оно не изменилось по сравнению с ситуацией, когда мы не задавали политику явно.
Далее, добавим следующий атрибут к методу хаба SendToGroup:
[Authorize(Roles = "user")]

Здесь мы не применили именованных политик. Мы указали, что метод можно выполнить только пользователю с ролью user, то есть, другими словами, роль user есть среди ролей, указанных в клейме role пользователя.
Наконец, добавим к методу AddUserToGroup такой атрибут:
[Authorize("AdminOnly")]

Здесь мы передали заданное имя политики.


  1. См главу об авторизации у Эндрю Лока

  2. Подробнее

  3. Подробнее о политиках авторизации здесь и далее по ссылкам 


Последнее обновление : 17 июня 2023 г.
Дата создания : 26 февраля 2023 г.

Комментарии

Комментарии