web-dev-qa-db-fra.com

InvalidOperationException: aucun authenticationScheme n'a été spécifié et aucun DefaultChallengeScheme n'a été trouvé

Nous avons un projet d'API Net Core 2.1. Nous utilisons les en-têtes de demande pour récupérer la clé API que nous vérifions dans notre base de données pour voir si elle correspond à l'une des clés attendues. Si c'est le cas, nous autorisons la demande à continuer, sinon nous voulons renvoyer une réponse non autorisée.

notre startup.cs

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

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

Notre 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;
                    }
                }
            }
        }
    }

et notre controller.cs

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

Tout fonctionne correctement lorsqu'une clé valide existe, mais dans le cas contraire, une erreur interne de 500 est lancée pour No authenticationScheme au lieu de 403.

Nous sommes relativement nouveaux sur Net Core (provenant de Net Framework/Forms Authentication), donc s'il existe un moyen plus précis de faire ce genre d'authentification, faites-le moi savoir.

Message d'erreur:

InvalidOperationException: aucun authenticationScheme n'a été spécifié et aucun DefaultChallengeScheme n'a été trouvé. Microsoft.AspNetCore.Authentication.AuthenticationService.ChallengeAsync (contexte HttpContext, schéma de chaînes, propriétés AuthenticationProperties)

6
scorpion5211

L'authentification basée sur les jetons est préférée. Cependant, si vous avez besoin d'un schéma ApiKeyAuth personnalisé, eh bien, c'est possible.

Premièrement, il semble que Authorize("APIKeyAuth") n'a pas de sens ici, car nous devons authentifier l'utilisateur avant l'autorisation. Lorsqu'il y a une demande entrante, le serveur n'a aucune idée de qui est l'utilisation. Donc, déplaçons le ApiKeyAuth de Authorization vers Authentication.

Pour ce faire, il suffit de créer un ApiKeyAuthOpts factice qui peut être utilisé pour contenir des options

public class ApiKeyAuthOpts : AuthenticationSchemeOptions
{
}

et un simple ApiKeyAuthHandler pour gérer l'authentification (je viens de copier certains de vos codes ci-dessus)::

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);
        }
    }
}

Enfin, nous avons encore besoin d'un peu de configuration pour les faire fonctionner:

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();
}

Lorsque vous envoyez une demande à une méthode d'action protégée par [Authorize]:

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

la réponse sera HTTP/1.1 200 OK. Lorsque vous envoyez une demande sans la bonne clé, la réponse sera:

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
4
itminus