簡體   English   中英

InvalidOperationException: 沒有指定 authenticationScheme,也沒有找到 DefaultChallengeScheme

[英]InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found

我們有一個 Net Core 2.1 API 項目。 我們使用請求標頭來檢索 API 密鑰,我們根據數據庫檢查該密鑰以查看它是否與預期的密鑰之一匹配。 如果是,那么我們允許請求繼續,否則我們想發回未經授權的響應。

我們的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
    {
    }

當有效密鑰存在時,一切正常,但如果不存在,則會為 No authenticationScheme 而不是 403 拋出 500 內部錯誤。

我們對 net core 相對較新(來自 Net Framework/Forms Authentication),所以如果有更准確的方法來進行這種身份驗證,請告訴我。

錯誤信息:

InvalidOperationException: 未指定 authenticationScheme,也未找到 DefaultChallengeScheme。 Microsoft.AspNetCore.Authentication.AuthenticationService.ChallengeAsync(HttpContext 上下文、字符串方案、AuthenticationProperties 屬性)

首選基於令牌的身份驗證。 但是,如果您確實需要自定義ApiKeyAuth方案,那么,這是可能的。

首先, Authorize("APIKeyAuth")在這里似乎沒有意義,因為我們必須在授權之前對用戶進行身份驗證。 當有傳入請求時,服務器不知道用戶是誰。 因此,讓我們將ApiKeyAuthAuthorization移動到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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM