InvalidOperationException: не указана схема authenticationScheme, и не найден DefaultChallengeScheme - PullRequest
0 голосов
/ 12 сентября 2018

У нас есть проект Net Core 2.1 API. Мы используем заголовки запроса для получения ключа API, который мы проверяем по нашей базе данных, чтобы проверить, соответствует ли он одному из ожидаемых ключей. Если это так, то мы разрешаем продолжить запрос, в противном случае мы хотим отправить обратно Несанкционированный ответ.

our startup.cs

services.AddAuthorization(options =>
            {
                options.AddPolicy("APIKeyAuth", policyCorrectUser =>
                {
                    policyCorrectUser.Requirements.Add(new APIKeyAuthReq());
                });

            });
services.AddSingleton<Microsoft.AspNetCore.Authorization.IAuthorizationHandler, APIKeyAuthHandler>();

Наш APIKeyAuthHandler.cs

public class APIKeyAuthReq : IAuthorizationRequirement { }

    public class APIKeyAuthHandler : AuthorizationHandler<APIKeyAuthReq>
    {
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, APIKeyAuthReq requirement)
        {
            if (context == null)
                throw new ArgumentNullException(nameof(context));
            if (requirement == null)
                throw new ArgumentNullException(nameof(requirement));

            var httpContext = context.Resource as Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext;

            var headers = httpContext.HttpContext.Request.Headers;
            if (headers.TryGetValue("Authorization", out Microsoft.Extensions.Primitives.StringValues value))
            {
                using (DBContext db = new DBContext ())
                {
                    var token = value.First().Split(" ")[1];
                    var login = db.Login.FirstOrDefault(l => l.Apikey == token);
                    if (login == null)
                    {
                        context.Fail();
                        httpContext.HttpContext.Response.StatusCode = 403;
                        return Task.CompletedTask;
                    } else
                    {
                        httpContext.HttpContext.Items.Add("CurrentUser", login);
                        context.Succeed(requirement);
                        return Task.CompletedTask;
                    }
                }
            }
        }
    }

и наш controller.cs

    [Route("api/[controller]/[action]")]
    [Authorize("APIKeyAuth")]
    [ApiController]
    public class SomeController : ControllerBase
    {
    }

Все работает нормально, когда существует действительный ключ, но когда его нет, возникает внутренняя ошибка 500 для параметра No authenticationScheme вместо 403.

Мы относительно новички в сетевом ядре (исходя из Net Framework / Forms Authentication), поэтому, если есть более точный способ выполнения такого рода аутентификации, пожалуйста, дайте мне знать.

Сообщение об ошибке:

InvalidOperationException: не указана схема аутентификации, и не было найдено DefaultChallengeScheme. Microsoft.AspNetCore.Authentication.AuthenticationService.ChallengeAsync (HttpContext контекст, строковая схема, свойства AuthenticationProperties)

1 Ответ

0 голосов
/ 12 сентября 2018

Аутентификация на основе токенов является предпочтительной. Однако, если вам нужна пользовательская схема ApiKeyAuth, это возможно.

Во-первых, кажется, что Authorize("APIKeyAuth") здесь не имеет смысла, так как мы должны аутентифицировать пользователя перед авторизацией. При поступлении входящего запроса сервер не знает, кто его использует. Итак, давайте переместим ApiKeyAuth с Authorization на Authentication.

Для этого просто создайте пустышку ApiKeyAuthOpts, которую можно использовать для хранения параметров

public class ApiKeyAuthOpts : AuthenticationSchemeOptions
{
}

и простой ApiKeyAuthHandler для обработки аутентификации (я просто скопировал некоторые из ваших кодов выше)::

public class ApiKeyAuthHandler : AuthenticationHandler<ApiKeyAuthOpts>
{
    public ApiKeyAuthHandler(IOptionsMonitor<ApiKeyAuthOpts> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) 
        : base(options, logger, encoder, clock)
    {
    }

    private const string API_TOKEN_PREFIX = "api-key";

    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        string token = null;
        string authorization = Request.Headers["Authorization"];

        if (string.IsNullOrEmpty(authorization)) {
            return AuthenticateResult.NoResult();
        }

        if (authorization.StartsWith(API_TOKEN_PREFIX, StringComparison.OrdinalIgnoreCase)) {
            token = authorization.Substring(API_TOKEN_PREFIX.Length).Trim();
        }

        if (string.IsNullOrEmpty(token)) {
            return AuthenticateResult.NoResult();
        }

        // does the token match ?
        bool res =false; 
        using (DBContext db = new DBContext()) {
            var login = db.Login.FirstOrDefault(l => l.Apikey == token);  // query db
            res = login ==null ? false : true ; 
        }

        if (!res) {
            return AuthenticateResult.Fail($"token {API_TOKEN_PREFIX} not match");
        }
        else {
            var id=new ClaimsIdentity( 
                new Claim[] { new Claim("Key", token) },  // not safe , just as an example , should custom claims on your own
                Scheme.Name 
            );
            ClaimsPrincipal principal=new ClaimsPrincipal( id);
            var ticket = new AuthenticationTicket(principal, new AuthenticationProperties(), Scheme.Name);
            return AuthenticateResult.Success(ticket);
        }
    }
}

Наконец, нам все еще нужно немного настроек, чтобы заставить их работать:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    services.AddAuthentication("ApiKeyAuth")
            .AddScheme<ApiKeyAuthOpts,ApiKeyAuthHandler>("ApiKeyAuth","ApiKeyAuth",opts=>{ });
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    // ...
    app.UseAuthentication();
    app.UseHttpsRedirection();
    app.UseMvc();
}

При отправке запроса к действию метод защищен [Authorize]:

GET https://localhost:44366/api/values/1 HTTP/1.1
Authorization: api-key xxx_yyy_zzz

ответ будет HTTP/1.1 200 OK. Когда вы отправляете запрос без правильного ключа, ответ будет:

HTTP/1.1 401 Unauthorized
Server: Kestrel
X-SourceFiles: =?UTF-8?B?RDpccmVwb3J0XDIwMThcOVw5LTEyXFNPLkFwaUtleUF1dGhcQXBwXEFwcFxhcGlcdmFsdWVzXDE=?=
X-Powered-By: ASP.NET
Date: Wed, 12 Sep 2018 08:33:23 GMT
Content-Length: 0
...