Интересно, я думаю, что это просто аутентификация , без каких-либо авторизаций (по крайней мере, не в вашем вопросе).Вы, конечно, хотите аутентифицировать клиента, но у вас, похоже, нет авторизации требований.Аутентификация - это процесс определения , кто делает этот запрос, а авторизация - это процесс определения того, что указанный запросчик может сделать , как только мы узнаем, кто это (подробнее здесь ).Вы указали, что хотите вернуть 401
(неверные учетные данные), а не 403
(неавторизованный), что, как мне кажется, подчеркивает разницу (больше здесь ).
Чтобы использовать собственную логику аутентификации в ASP.NET Core, вы можете написать свою собственную AuthenticationHandler
, которая отвечает за принятие запроса и определение User
.Вот пример для вашей ситуации:
public class ClientTokenHandler : AuthenticationHandler<ClientTokenOptions>
{
private readonly string[] _clientTokens;
public ClientTokenHandler(IOptionsMonitor<ClientTokenOptions> optionsMonitor,
ILoggerFactory loggerFactory, UrlEncoder urlEncoder, ISystemClock systemClock,
IConfiguration config)
: base(optionsMonitor, loggerFactory, urlEncoder, systemClock)
{
_clientTokens = config.GetSection("ClientTokens").Get<string[]>();
}
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var tokenHeaderValue = (string)Request.Headers["X-TOKEN"];
if (string.IsNullOrWhiteSpace(tokenHeaderValue))
return Task.FromResult(AuthenticateResult.NoResult());
if (!_clientTokens.Contains(tokenHeaderValue))
return Task.FromResult(AuthenticateResult.Fail("Unknown Client"));
var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(
Enumerable.Empty<Claim>(),
Scheme.Name));
var authenticationTicket = new AuthenticationTicket(claimsPrincipal, Scheme.Name);
return Task.FromResult(AuthenticateResult.Success(authenticationTicket));
}
}
Вот описание того, что происходит в HandleAuthenticateAsync
:
- Заголовок
X-TOKEN
извлекается из запроса.Если это неверно, мы указываем, что не можем аутентифицировать запрос (подробнее об этом позже). - Значение, полученное из заголовка
X-TOKEN
, сравнивается с известным списком клиентских токенов.Если это неуспешно, мы указываем, что аутентификация не удалась (мы не знаем, кто это - об этом позже). - Когда токен клиента совпадает с заголовком запроса
X-TOKEN
, мы создаем новыйAuthenticationTicket
/ ClaimsPrincipal
/ ClaimsIdentity
комбо.Это наше представление User
- вы можете включить свои Claim
вместо использования Enumerable.Empty<Claim>()
, если вы хотите связать дополнительную информацию с клиентом.
Вы должны иметь возможностьпо большей части используйте это как есть, с небольшими изменениями (я упростил, чтобы ответ был кратким и заполнял несколько пробелов в вопросе):
- Конструктор берет экземпляр
IConfiguration
как последний параметр, который затем используется для чтения string[]
из, в моем примере, appsettings.json
.Вы, вероятно, делаете это по-другому, так что вы можете просто использовать DI для ввода того, что вы в данный момент используете здесь, при необходимости. - Я жестко закодировал
X-TOKEN
в качестве имени заголовка для использования при извлечениимаркер.Скорее всего, вы сами будете использовать другое имя для этого, и по вашему вопросу я вижу, что вы его не программируете, что лучше.
Еще одна вещь, которую стоит отметить в этой реализации, этоиспользование AuthenticateResult.NoResult()
и AuthenticateResult.Fail(...)
.Первое указывает, что у нас не было достаточно информации для выполнения аутентификации, а второе указывает, что у нас было все необходимое, но аутентификация не удалась.Для простой установки, подобной вашей, я думаю, что вы будете в порядке, используя Fail
в обоих случаях, если вы предпочтете.
Второе, что вам нужно, это класс ClientTokenOptions
, который используетсявыше в AuthenticationHandler<ClientTokenOptions>
.В данном примере это однострочный текст:
public class ClientTokenOptions : AuthenticationSchemeOptions { }
. Он используется для настройки вашего AuthenticationHandler
- не стесняйтесь переносить некоторые настройки сюда (например, _clientTokens сверху).Это также зависит от того, насколько настраиваемым и повторно используемым вы хотите, чтобы это было - в качестве другого примера, вы можете определить здесь имя заголовка, но это ваше дело.
Наконец, чтобы использовать ClientTokenHandler
, вы 'Вам нужно будет добавить следующее к ConfigureServices
:
services.AddAuthentication("ClientToken")
.AddScheme<ClientTokenOptions, ClientTokenHandler>("ClientToken", _ => { });
Здесь мы просто регистрируем ClientTokenHandler
как AuthenticationHandler
в соответствии с нашей собственной ClientToken
схемой.Я бы не стал жестко кодировать "ClientToken"
здесь, как это, но, опять же, это просто упрощение.Напуганный _ => { }
в конце - это обратный вызов, которому дается экземпляр ClientTokenOptions
для изменения: нам здесь это не нужно, так что это всего лишь пустая лямбда.
InvalidOperationException: Не указана схема authenticationScheme, и не найдено DefaultChallengeScheme.
"DefaultChallengeScheme" в вашем сообщении об ошибке теперь установлено с вызовом services.AddAuthentication("ClientToken")
выше ("ClientToken" - это имя схемы).
Если вы хотите использовать этот подход, вам нужно удалить свои ClientTokenRequirement
вещи.Вам также может быть интересно взглянуть на проект BasicAuthentication Барри Дорранса - он следует тем же шаблонам, что и официальный ASP.NET Core AuthenticationHandler
s, но проще для начала.Если вас не интересуют аспекты конфигурируемости и повторного использования, предоставленная мною реализация должна соответствовать цели.