Как правильно настроить Политику авторизации для WEB API в .NET Core - PullRequest
0 голосов
/ 24 августа 2018

У меня есть этот проект веб-API, без пользовательского интерфейса.В моем файле appsettings.json есть раздел, в котором перечислены токены и клиент, к которому они принадлежат.Таким образом, клиент должен будет просто представить соответствующий токен в заголовке.Если токен отсутствует или недействителен, он должен возвращать 401.

В ConfigureServices я настраиваю авторизацию

.AddTransient<IAuthorizationRequirement, ClientTokenRequirement>()
.AddAuthorization(opts => opts.AddPolicy(SecurityTokenPolicy, policy =>
 {
       var sp = services.BuildServiceProvider();
       policy.Requirements.Add(sp.GetService<IAuthorizationRequirement>());
 }))

Эта часть запускается правильно из того, что я вижу.Вот код для ClientTokenRequirement

protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ClientTokenRequirement requirement)
    {
        if (context.Resource is AuthorizationFilterContext authFilterContext)
        {
            if (string.IsNullOrWhiteSpace(_tokenName))
                throw new UnauthorizedAccessException("Token not provided");

            var httpContext = authFilterContext.HttpContext;

            if (!httpContext.Request.Headers.TryGetValue(_tokenName, out var tokenValues))
                return Task.CompletedTask;

            var tokenValueFromHeader = tokenValues.FirstOrDefault();

            var matchedToken = _tokens.FirstOrDefault(t => t.Token == tokenValueFromHeader);

            if (matchedToken != null)
            {       
                httpContext.Succeed(requirement);
            }
        }

        return Task.CompletedTask;
    }

Когда мы находимся в ClientTokenRequirement и не сопоставили токен, он возвращает

return Task.CompletedTask;

Это делается так, как описано в https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-2.1

Это работает правильно, когда есть действительный токен, но когда его нет, и он возвращает Task.Completed, нет 401, но вместо этого есть исключение

InvalidOperationException: никакой аутентификацииScheme не былоуказан, и DefaultChallengeScheme не найден.

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

1 Ответ

0 голосов
/ 27 августа 2018

Интересно, я думаю, что это просто аутентификация , без каких-либо авторизаций (по крайней мере, не в вашем вопросе).Вы, конечно, хотите аутентифицировать клиента, но у вас, похоже, нет авторизации требований.Аутентификация - это процесс определения , кто делает этот запрос, а авторизация - это процесс определения того, что указанный запросчик может сделать , как только мы узнаем, кто это (подробнее здесь ).Вы указали, что хотите вернуть 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:

  1. Заголовок X-TOKEN извлекается из запроса.Если это неверно, мы указываем, что не можем аутентифицировать запрос (подробнее об этом позже).
  2. Значение, полученное из заголовка X-TOKEN, сравнивается с известным списком клиентских токенов.Если это неуспешно, мы указываем, что аутентификация не удалась (мы не знаем, кто это - об этом позже).
  3. Когда токен клиента совпадает с заголовком запроса X-TOKEN, мы создаем новыйAuthenticationTicket / ClaimsPrincipal / ClaimsIdentity комбо.Это наше представление User - вы можете включить свои Claim вместо использования Enumerable.Empty<Claim>(), если вы хотите связать дополнительную информацию с клиентом.

Вы должны иметь возможностьпо большей части используйте это как есть, с небольшими изменениями (я упростил, чтобы ответ был кратким и заполнял несколько пробелов в вопросе):

  1. Конструктор берет экземплярIConfiguration как последний параметр, который затем используется для чтения string[] из, в моем примере, appsettings.json.Вы, вероятно, делаете это по-другому, так что вы можете просто использовать DI для ввода того, что вы в данный момент используете здесь, при необходимости.
  2. Я жестко закодировал 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, но проще для начала.Если вас не интересуют аспекты конфигурируемости и повторного использования, предоставленная мною реализация должна соответствовать цели.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...