Как я могу иметь несколько методов аутентификации, при которых только один должен быть успешным?

#c# #asp.net-core

#c# #asp.net-core

Вопрос:

К сожалению, я не могу найти ничего, что помогло бы мне в этом вопросе.

Обычно API использует OIDC для обработки аутентификации, поэтому .AddAuthentication.AddJwtBearer по умолчанию используется обычный. Это работает нормально, НО нам также необходимо иметь другой способ аутентификации, который обеспечивал бы идентификацию пользователя, поскольку токен не всегда доступен. Допустим, это будет некоторый ApiKey со словарем пользователей для данного ApiKey.

Тем не менее, я хотел бы, чтобы токен JWT имел приоритет над этим другим уровнем, потому что это просто более сильный вид аутентификации. Так что это было бы что-то вроде этого:

  1. Мы получаем запрос
  2. Если мы видим заголовок авторизации, это означает, что запрос предоставляет токен — мы хотим проверить этот токен, если он недействителен — мы хотим 401, потому что что-то не так.
  3. Если в HttpRequest нет заголовка авторизации, но у нас есть заголовок ApiKey — мы хотим полностью исключить токены проверки и попытаться проверить этот ApiKey. Если он действителен, мы хотим установить HttpContext.Пользователь оттуда и сообщает, что вся аутентификация прошла нормально, и наши контроллеры могут использовать HttpContext.Пользователь обычно.

В настоящее время у нас есть несколько конечных точек контроллера, которые поддерживают сценарий token ИЛИ ApiKey (другой атрибут), но когда увеличивается количество контроллеров и конечных точек, дублировать все может быть довольно сложно.

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

 OnMessageReceived = async context =>
{
    if (!context.Request.Headers.ContainsKey("Authorization") 
        amp;amp; context.Request.Headers.ContainsKey("ApiKey"))
    {
         // some logic to check that ApiKey
         context.Principal = new ClaimsPrincipal(new ClaimsIdentity(
             new Claim[]
             {
                 new Claim(ClaimTypes.NameIdentifier, "ApiKeyUser")
             }));
         context.Success();
    }
    await Task.CompletedTask;
}
 

К сожалению, промежуточное программное обеспечение все равно настаивает на проверке токена и возврате 401, так что, скорее всего, это неверный подход к решению этой проблемы.

Ответ №1:

Я нашел способ, поэтому я оставлю это здесь. Вы можете просто использовать AddPolicyScheme для добавления селектора, который будет использовать разные обработчики, вызывая их по имени.

Например:

 services.AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme = smartAuthenticationScheme; // just some string
            options.DefaultChallengeScheme = smartAuthenticationScheme;
            options.DefaultScheme = smartAuthenticationScheme;
        })
        .AddPolicyScheme(smartAuthenticationScheme, "Token / Custom", options =>
        {
            options.ForwardDefaultSelector = context =>
            {
                var header = (string)context.Request.Headers["Authorization"];

                if (header == null || string.IsNullOrEmpty(header))
                    return undefinedAuthenticationScheme;

                if (header.StartsWith("Bearer"))
                    return bearerAuthenticationScheme; // it's just JwtBearerDefaults.AuthenticationScheme
                if (header.StartsWith("Custom"))
                    return customAuthenticationScheme;

                return undefinedAuthenticationScheme;
            };
        })
        .AddJwt(bearerAuthenticationScheme, configuration)
        .AddCustom(customAuthenticationScheme)
        .AddUndefined(undefinedAuthenticationScheme);
 

И таким образом наше промежуточное программное обеспечение будет просматривать содержимое заголовка авторизации и вызывать AuthenticationHandler, который зарегистрирован под указанным именем. UndefinedHandler существует только для обслуживания ответа 401, когда содержимое заголовка авторизации не соответствует ни Jwt, ни пользовательскому AuthorizationHandler.