【问题标题】:InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme foundInvalidOperationException:没有指定 authenticationScheme,也没有找到 DefaultChallengeScheme
【发布时间】:2018-09-12 04:33:07
【问题描述】:

我们有一个 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 属性)

【问题讨论】:

  • 我认为您应该考虑使用基于令牌的身份验证。您可以查看此链接-stackoverflow.com/questions/38661090/…
  • 似乎基于令牌的身份验证适用于您有用户名/密码开始的情况?我们的应用程序将基于存储在数据库中的静态 API 密钥。我相信基于令牌的身份验证在我们的案例中不起作用,除非我遗漏了一些东西
  • 我建议您为您的身份验证方法创建一个身份验证方案。然后,您可以根据您的方案创建的用户主体进行授权。例如。 joonasw.net/view/creating-auth-scheme-in-aspnet-core-2
  • 删除[Authorize("APIKeyAuth")]

标签: c# asp.net-core .net-core asp.net-core-2.0 asp.net-core-2.1


【解决方案1】:

首选基于令牌的身份验证。但是,如果您确实需要自定义 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

【讨论】:

  • 这太完美了!非常感谢您花时间详细解释!一个问题 - 在 User.Identity.Name 中填充登录名的正确方法是什么?在我使用httpContext.HttpContext.Items.Add("CurrentUser", login); 之前,但我相信有更好的方法来做到这一点?
  • @Gio ,当认证成功时,我们需要设置AuthenticationTicket ,它实际上是ClaimsPrincipalAuthenticatonProperties 的组合。我们可以使用ClaimsIdentity 创建ClaimsPrincipalClaimsIdentity 有一个属性 Name 代表当前用户。
猜你喜欢
  • 2019-11-02
  • 2018-02-26
  • 2021-10-18
  • 2019-03-22
  • 1970-01-01
  • 2021-10-11
  • 2019-08-02
  • 2021-05-24
  • 1970-01-01
相关资源
最近更新 更多