Использовать множественную аутентификацию носителя JWT

87

Можно ли поддерживать несколько эмитентов токенов JWT в ASP.NET Core 2? Я хочу предоставить API для внешней службы, и мне нужно использовать два источника токенов JWT - Firebase и настраиваемые эмитенты токенов JWT. В ядре ASP.NET я могу установить аутентификацию JWT для схемы аутентификации Bearer, но только для одного центра:

  services
        .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            options.Authority = "https://securetoken.google.com/my-firebase-project"
            options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidIssuer = "my-firebase-project"
                    ValidateAudience = true,
                    ValidAudience = "my-firebase-project"
                    ValidateLifetime = true
                };
        }

У меня может быть несколько эмитентов и аудиторий, но я не могу установить несколько авторитетов.

В своем уме
источник
1
AFAIK вы можете добавить любое количество свойств в JWT. Итак, ничто не мешает вам записать два имени эмитента в JWT. Проблема заключается в том, что вашему приложению необходимо знать оба ключа, если каждый эмитент использовал свой ключ для подписи.
Тим Бигелейзен

Ответы:

186

Вы можете полностью достичь того, чего хотите:

services
    .AddAuthentication()
    .AddJwtBearer("Firebase", options =>
    {
        options.Authority = "https://securetoken.google.com/my-firebase-project"
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidIssuer = "my-firebase-project"
            ValidateAudience = true,
            ValidAudience = "my-firebase-project"
            ValidateLifetime = true
        };
    })
    .AddJwtBearer("Custom", options =>
    {
        // Configuration for your custom
        // JWT tokens here
    });

services
    .AddAuthorization(options =>
    {
        options.DefaultPolicy = new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .AddAuthenticationSchemes("Firebase", "Custom")
            .Build();
    });

Давайте рассмотрим различия между вашим кодом и этим.

AddAuthentication не имеет параметра

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

Используйте другую перегрузку AddJwtBearer

Каждый AddXXXметод добавления аутентификации имеет несколько перегрузок:

Теперь, поскольку вы используете один и тот же метод аутентификации дважды, но схемы аутентификации должны быть уникальными, вам необходимо использовать вторую перегрузку.

Обновите политику по умолчанию

Поскольку запросы больше не будут аутентифицироваться автоматически, добавление [Authorize]атрибутов к некоторым действиям приведет к тому, что запросы будут отклонены и HTTP 401будет выдан.

Так что это не то , что мы хотим , потому что мы хотим , чтобы обработчики проверки подлинности возможности аутентификации запроса, мы изменим политику по умолчанию системы авторизации, указав оба Firebaseи Customаутентификацию схемы должно быть пытался проверить подлинность запроса.

Это не мешает вам более ограничивать некоторые действия; у [Authorize]атрибута есть AuthenticationSchemesсвойство, позволяющее переопределить допустимые схемы аутентификации.

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

Представим, что некоторые действия доступны только для токенов JWT, выпущенных Firebase, и должны иметь заявку с определенным значением; вы можете сделать это так:

// Authentication code omitted for brevity

services
    .AddAuthorization(options =>
    {
        options.DefaultPolicy = new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .AddAuthenticationSchemes("Firebase", "Custom")
            .Build();

        options.AddPolicy("FirebaseAdministrators", new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .AddAuthenticationSchemes("Firebase")
            .RequireClaim("role", "admin")
            .Build());
    });

Затем вы можете использовать [Authorize(Policy = "FirebaseAdministrators")]для некоторых действий.

И последнее, на что следует обратить внимание: если вы перехватываете AuthenticationFailedсобытия и используете что-либо, кроме первой AddJwtBearerполитики, вы можете увидеть, IDX10501: Signature validation failed. Unable to match key...что это вызвано тем, что система проверяет каждое AddJwtBearerпо очереди, пока не найдет совпадение. Ошибка обычно игнорируется.

Микаэль Дерри
источник
4
Требуется ли для этого изменить значение заголовка из firebase или специального решения? т.е. а не Authorization : Bearer <token>что заголовок быть Authorization : Firebase <token>например? Когда я попробовал это решение, я получил сообщение об ошибке: «Для схемы« Bearer »не зарегистрирован обработчик аутентификации».
Раш Фрисби
4
Нет, заголовки менять не нужно. Сообщение об ошибке предполагает, что вы имеете в виду несуществующую схему аутентификации (носитель). В наших примерах две зарегистрированные схемы - это Firebase и Custom, которые являются первыми аргументами .AddJwtBearerвызовов методов.
Mickaël Derriey
5
Здравствуй. Искал именно такое решение. К сожалению, я получаю исключение «Схема проверки подлинности не указана, и схема DefaultChallengeScheme не найдена». options.DefaultPolicy установлен нормально. Любые идеи?
terjetyl 05
11
Это был в высшей степени полезный ответ, и он собрал воедино многое из того, что я видел повсюду.
Арон В.
2
@TylerOhlsen, это неверно; хотя он и будет использоваться в описанном вами случае, он не единственный. Он также будет использоваться, если вы не укажете требование авторизации на уровне конечной точки, но украсите контроллеры и / или действия MVC пустым [Authorize]атрибутом.
Микаэль Дерри
4

Это продолжение ответа Микаэля Дерри.

В нашем приложении есть настраиваемое требование авторизации, которое мы решаем из внутреннего источника. Мы использовали Auth0, но переходим на аутентификацию учетной записи Microsoft с использованием OpenID. Вот слегка отредактированный код из нашего запуска ASP.Net Core 2.1. Для будущих читателей это работает на момент написания этой статьи для указанных версий. Вызывающий использует id_token из OpenID для входящих запросов, переданных как Bearer token. Надеюсь, это поможет кому-то другому, пытающемуся преобразовать авторитет личности, так же, как этот вопрос и ответ помогли мне.

const string Auth0 = nameof(Auth0);
const string MsaOpenId = nameof(MsaOpenId);

string domain = "https://myAuth0App.auth0.com/";
services.AddAuthentication()
        .AddJwtBearer(Auth0, options =>
            {
                options.Authority = domain;
                options.Audience = "https://myAuth0Audience.com";
            })
        .AddJwtBearer(MsaOpenId, options =>
            {
                options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
                {
                    ValidateAudience = true,
                    ValidAudience = "00000000-0000-0000-0000-000000000000",

                    ValidateIssuer = true,
                    ValidIssuer = "https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0",

                    ValidateIssuerSigningKey = true,
                    RequireExpirationTime = true,
                    ValidateLifetime = true,
                    RequireSignedTokens = true,
                    ClockSkew = TimeSpan.FromMinutes(10),
                };
                options.MetadataAddress = "https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0/.well-known/openid-configuration";
            }
        );

services.AddAuthorization(options =>
{
    options.DefaultPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .AddAuthenticationSchemes( Auth0, MsaOpenId )
        .Build();

    var approvedPolicyBuilder =  new AuthorizationPolicyBuilder()
           .RequireAuthenticatedUser()
           .AddAuthenticationSchemes(Auth0, MsaOpenId)
           ;

    approvedPolicyBuilder.Requirements.Add(new HasApprovedRequirement(domain));

    options.AddPolicy("approved", approvedPolicyBuilder.Build());
});
Нет возврата, нет возврата
источник