.Net SignalR использует проверку подлинности на предъявителя JWT, когда также настроена проверка подлинности файлов cookie

#c# #asp.net-core #authentication #signalr #bearer-token

Вопрос:

У меня есть ASP.NET 5 Веб-приложение, которое является частью более крупной системы и использует аутентификацию файлов cookie для запросов браузера.

Я хочу добавить возможность запрашивать данные и выполнять определенные действия в определенных службах Windows, которые также являются частью общей системы и выполняются на нескольких отдельных компьютерах. Я хочу использовать для этого SignalR. Затем службы Windows запускаются как выделенный идентификатор службы, который является частью нашего ActiveDirectory. Поскольку службы не должны хранить свои учетные данные пользователя в коде или локальных файлах конфигурации, они запрашивают маркер аутентификации для веб-приложения из API, который работает с проверкой подлинности Windows.

Затем, при установлении соединения SignalR с веб-приложением, службы будут использовать токен, полученный от API, для аутентификации в веб-приложении. В целом это работает.

Конфигурация аутентификации веб — приложения является:

 services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
        .AddCookie(options =>
        {
            options.LoginPath = "/Login";
            options.ExpireTimeSpan = TimeSpan.FromMinutes(12);
            options.SlidingExpiration = true;
        })
        .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, opt =>
        {
            // Configuration details excluded 
            // ...
            opt.Events = new JwtBearerEvents
            {
                OnMessageReceived = context =>
                {
                    // ...
                }
            };
 

Согласно документации Microsoft, это должна быть конфигурация аутентификации vaild.

В services.AddAuthorization(...) методе я добавил политику, специфичную для схемы на предъявителя:

 options.AddPolicy("SignalRService", policy =>
{
    policy.RequireRole("SignalRService"); 
    policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
});
 

И затем существует метод концентратора SignalR, защищенный этой политикой:

 [Authorize(Policy = "SignalRService")]
public async Task RegisterService(string clientIdString) { /*...*/ }
 

And finally the hub connection in the windows service is created as follows:

 connection = new HubConnectionBuilder()
    .WithUrl(hubAddress, options =>
    {
        options.AccessTokenProvider = () => Task.FromResult(authToken);
    })
    .WithAutomaticReconnect()
    .Build();
 

Establishing the connection works:

 await connection.StartAsync();
 

Но когда я пытаюсь вызвать метод концентратора из службы Windows, например await connection.InvokeAsync("RegisterService", clientId); , я получаю HubException сообщение:

Не удалось вызвать «registerService», потому что пользователь не авторизован

Я также создал контроллер API в веб-приложении для целей тестирования и обеспечил его той же политикой:

 [HttpGet]
[Authorize(Policy = "SignalRService")]
public IActionResult Get()
{
    return Ok(User.Identity.Name);
}
 

Когда я вызываю эту конечную точку API с тем же маркером, который я использовал бы для вызова концентратора SignalR, я получаю идентификатор, установленный для возвращенного токена, как и ожидалось. Я также проверил, что настроенный OnMessageReceived обработчик событий выполняется в этом сценарии, в то время как это не так, когда я использую соединение SignalR.

Когда я устанавливаю JwtBearerDefaults.AuthenticationScheme в качестве схемы по умолчанию Startup.cs вместо CookieAuthenticationDefaults.AuthenticationScheme этого, она также работает с концентратором SignalR, но затем моя стандартная аутентификация пользователя на основе файлов cookie прерывается.

Я ожидаю, что существует некоторая дополнительная конфигурация, необходимая для того, чтобы веб-приложение явно использовало схему носителя при вызове метода концентратора, но пока я ничего не смог найти.

Ответ №1:

После отчаянных попыток в течение еще одного часа я обнаружил, что конкретная аутентификация на предъявителя работала с аутентификацией файлов cookie по умолчанию, когда я ставил Authorize(Policy = "SignalRService") ее непосредственно в класс, а не в метод.

Поскольку мой концентратор также должен быть доступен для подключений к браузеру с использованием файлов cookie, я, наконец, получил:

 [Authorize(AuthenticationSchemes = "Bearer,Cookies")]
public class SignalRServiceHub : Hub
{
    
    [Authorize(Policy = "SignalRService")]
    public async Task RegisterService(string clientIdString)
    {
        // ...
    }

    [Authorize(Policy = "Root")]
    public async Task RegisterMonitoringClient()
    {
        // ...
    }
 

Я не совсем уверен, почему в этом случае необходимо указывать схемы на уровне класса, в то время как это не относится к ApiController реализациям