Можно ли поддерживать несколько эмитентов токенов JWT в ASP.NET Core 2? Я хочу предоставить API для внешней службы, и мне нужно использовать два источника токенов JWT - Firebase и настраиваемые эмитенты токенов JWT. В ядре ASP.NET я могу установить аутентификацию JWT для схемы аутентификации на предъявителя, но только для одного центра:
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
};
}
У меня может быть несколько эмитентов и аудиторий, но я не могу установить несколько органов власти.
Вы можете полностью достичь того, чего хотите:
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
по очереди, пока не найдет совпадение. Обычно на ошибку можно не обращать внимания.
Требуется ли для этого изменить значение заголовка из firebase или специального решения? т.е. вместо Authorization : Bearer <token>
, чтобы заголовок был, например, Authorization : Firebase <token>
? Когда я попробовал это решение, я получил ошибку: «Для схемы« Bearer »не зарегистрирован обработчик аутентификации».
Нет, заголовки менять не нужно. Сообщение об ошибке предполагает, что вы имеете в виду несуществующую схему аутентификации (носитель). В наших примерах две зарегистрированные схемы - это Firebase и Custom, которые являются первыми аргументами вызовов метода .AddJwtBearer
.
Это произошло потому, что кто-то поместил [Authorize (AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] в методы контроллера. Это работает после того, как я изменил его на [Авторизовать]
Есть ли какой-нибудь образец программы или ее можно скачать с github или где-нибудь для справки?
Привет. Искал именно такое решение. К сожалению, я получаю исключение «Схема проверки подлинности не указана, и схема DefaultChallengeScheme не найдена». options.DefaultPolicy установлен нормально. Любые идеи?
Это был в высшей степени полезный ответ, и он собрал воедино многое из того, что я видел повсюду.
@AronW. У меня такая же ошибка. Вы нашли решение для ошибки, связанной с отсутствием схемы по умолчанию? пожалуйста, дайте мне знать
Я тоже нашел этот ответ СУПЕР ПОЛЕЗНЫМ. Если бы я мог проголосовать за самый высший ответ, который я когда-либо видел, то это был бы этот. У меня он работает, но нужно очистить код. У меня есть несколько дополнительных фрагментов, которые могут быть полезны другим читателям, поэтому оставлю это как дополнительный ответ. В ASP.Net Core 3.1 вы получаете несколько неприятных граммов, которые загромождают журнал, но после их прочтения вы можете увидеть, что они имеют смысл и не о чем беспокоиться. stackoverflow.com/questions/58856735/… имеет неприятные граммы, которые я вижу.
Я получаю следующую ошибку в ядре 3.1: «Не указана схема проверки подлинности, и схема DefaultAuthenticateScheme не найдена». Если я правильно понимаю, вы не хотите указывать схему по умолчанию. Как решить эту проблему
@Pieter, я думаю, что ни в атрибуте [Authorize]
, ни в политике авторизации явно не указаны схемы аутентификации, которые будут использоваться для аутентификации.
В ядре 3.1 .AddAuthentication () должна иметь схему по умолчанию. auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; auth.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; auth.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; И, кстати, порядок в de AddAuthorization имеет значение, если я прав
@Pieter, это неверно, схемы по умолчанию не требуются, как указано в моем ответе. Ваши схемы также показывают, что ваша установка никак не связана с вопросом. При необходимости задайте новый вопрос.
Испытываются ли оба AuthenticationScheme
для каждого звонка? (Мне не звонят ни один из моих .AddJwtBeaerer
.)
Политика авторизации определяет, какие схемы аутентификации «пробуются». Взяв последний пример кода выше, пустой атрибут [Authorize]
будет использовать политику по умолчанию, следовательно, будет пробовать обе схемы, тогда как [Authorize(Policy = "FirebaseAdministrators")]
будет пробовать только схему Firebase. Отсутствие политики на уровне действия, контроллера или глобального уровня означает, что конечная точка доступна для анонимных запросов, поэтому никакая схема не будет опробована.
В ядре 3.1 DefaultPolicy используется только тогда, когда каждый тип конечной точки в ваших Startup UseEndpoints устанавливает RequireAuthorization (). (например, .UseEndpoints(rb => rb.MapControllers().RequireAuthorization())
)
@TylerOhlsen, это неверно; хотя он и будет использоваться в описанном вами случае, он не единственный. Он также будет использоваться, если вы не укажете требование авторизации на уровне конечной точки, но украсите контроллеры и / или действия MVC пустым атрибутом [Authorize]
.
Опыт, который я получаю, заключается в том, что обе конфигурации схемы проверяются, и любой сбой приводит к отключению всего запроса. Первый соответствует предоставленному токену, и я вижу, что OnTokenValidated выполняется. Затем по какой-то глупой причине он снова переходит к проверке, используя вторую схему, которая, конечно, терпит неудачу, и создается 401. Не знаю, для какого сценария это предназначено. Кто выдает токены, поддерживающие несколько схем ??!
@Sam Я создал совершенно новый проект, следуя концепции моего ответа, и я не могу воспроизвести то, что вы испытываете. Да, обе схемы проверяются, что ожидается, но поскольку одна из них завершается успешно, общая авторизация считается успешной. См. Самый простой пример, который я мог придумать, в gist.github.com/mderriey/994d72a81f9f4324b32e68fec0705a2e.
@ MickaëlDerriey Мы делаем что-то подобное вышеупомянутому. Кажется, это работает для проверки подлинности, но в итоге получается сценарий, в котором ClaimsPrincipal либо неправильно установлен, либо утверждения и удостоверения в ClaimsPrincipal не установлены. Есть идеи, почему это должно быть?
этот ответ определенно направил меня в правильное русло, но я не смог заставить его работать и последовал за этой статьей. установка по умолчанию сработала docs.microsoft.com/en-us/aspnet/core/security/authorization/…
К вашему сведению, вам может потребоваться специальное промежуточное ПО для заполнения context.User, см. github.com/aspnet/Security/issues/1708#issuecomment-37656749 1
Это продолжение ответа Микаэля Дерри.
В нашем приложении есть настраиваемое требование авторизации, которое мы решаем из внутреннего источника. Мы использовали 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());
});
AFAIK вы можете добавить любое количество свойств в JWT. Итак, ничто не мешает вам записать два имени эмитента в JWT. Проблема заключается в том, что вашему приложению необходимо знать оба ключа, если каждый эмитент использовал свой ключ для подписи.