没有指定 authenticationScheme,也没有找到 DefaultChallengeScheme - ASP.NET core 2.1

No authenticationScheme was specified, and there was no DefaultChallengeScheme found - ASP.NET core 2.1

我正在使用 ASP.NET Core 2.1 WebApi 项目,因为我们使用了基于令牌的身份验证

public class UserIdentityFilter : IAuthorizationFilter
{    
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        StringValues authorizationHeaders;
        if (!context.HttpContext.Request.Headers.TryGetValue("Authorization", out authorizationHeaders))
            return;
        ...
        ...
    }   
}

并具有用于错误处理的中间件:

public async Task Invoke(HttpContext context, ILogger logger, IAppConfiguration appConfiguration)
{            
    try
    {
        await _next(context);
    }
    catch (Exception ex)
    {               
        await HandleExceptionAsync(context, ex, logger, appConfiguration);
    }
}  

如果我通过 header 授权方法,它工作正常,但是缺少 header 相同会给出错误 No authenticationScheme was specified, and there was no DefaultChallengeScheme found.

这里我有两个问题:

1) header 未指定时,发送 500 异常到用户端可以吗?

2) 如何处理这种情况并传递有意义的消息 "header is missing " 之类的?

It is okay to send 500 with this exception to user end, when header not specified?

恐怕这不是个好主意。

500 状态代码表示存在服务器错误。当客户端发送没有令牌的请求时,告诉客户端 "an internal error happens" 是没有意义的。更好的方法是发送401来挑战用户或发送403来禁止.

How to handle this scenario and passing meaningful message "header is missing" or something?

首先,我不得不说我不认为使用 AuthorizationFilter 来验证用户是一个好的选择。

如错误所述,错误被抛出是因为没有指定 AuthenticationScheme,也没有找到 DefaultChallengeScheme

要修复此错误,只需指定身份验证方案。例如,如果您使用 JwtToken,则应添加 AddAuthentication(JwtBearerDefaults.AuthenticationScheme) 或使用 [Authorize(AuthenticationSchemes ="JwtBearerDefaults.AuthenticationScheme")] 属性

否则,如果您想自定义它对用户进行身份验证的方式(例如,自定义 token-based 身份验证),您应该创建一个新的令牌身份验证处理程序。已经有一个 built-in 摘要 AuthenticationHandler class :

public abstract class AuthenticationHandler<TOptions> : IAuthenticationHandler 
    where TOptions : AuthenticationSchemeOptions, new()
{
     // ...
}

由于默认 HandleChallengeAsync() 将发送 401 响应,您可以简单地扩展 AuthenticationHandler 并覆盖 HandleChallengeAsync() 方法来自定义您的自己留言挑战用户 :

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


    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        StringValues authorizationHeaders;
        if (!context.HttpContext.Request.Headers.TryGetValue("Authorization", out authorizationHeaders))
             return AuthenticateResult.NoResult();
        // ... return AuthenticateResult.Fail(exceptionMessage);
        // ... return AuthenticateResult.Success(ticket)
    } 

    protected override Task HandleChallengeAsync(AuthenticationProperties properties)
    {
        Response.StatusCode = 401;
        var message = "tell me your token";
        Response.Body.Write(Encoding.UTF8.GetBytes(message));
        return Task.CompletedTask;
    }

    protected override Task HandleForbiddenAsync(AuthenticationProperties properties)
    {
        Response.StatusCode = 403;
        var message = "you have no rights";
        Response.Body.Write(Encoding.UTF8.GetBytes(message));
        return Task.CompletedTask;
    }

}

最后,您还需要注册身份验证处理程序:

services.AddAuthentication("OurOwnAuthN")
        .AddScheme<OurOwnAuthNOpts,OurOwnAuthNHandler>("OurOwnAuthN","Our Own AuthN Scheme",opts=>{
            // ...
        });

如果您不想将 "OurOwnAuthN" 设置为默认身份验证方案,您可以使用 [Authorize(AuthenticationSchemes ="OurOwnAuthN")] 来保护您的资源:

// your `ConfigureServices()`
services.AddAuthentication()
        .AddScheme<OurOwnAuthNOpts,OurOwnAuthNHandler>("OurOwnAuthN","Our Own AuthN Scheme",opts=>{
            // ...
        });


// your action method :
// GET api/values/5
[Authorize(AuthenticationSchemes ="OurOwnAuthN")]        
[HttpGet("{id}")]
public ActionResult<string> Get(int id)
{
    return "value";
}

如果用户发送没有令牌或令牌不正确的请求,服务器的响应将是:

HTTP/1.1 401 Unauthorized
Transfer-Encoding: chunked
Server: Kestrel
X-SourceFiles: =?UTF-8?B?RDpccmVwb3J0XDIwMThcMTBcMThcU08uYXV0aGVudGljYXRpb25TY2hlbWUsIE5vIERlZmF1bHRDaGFsbGVuZ2VTY2hlbWVcQXBwXEFwcFxhcGlcdmFsdWVzXDE=?=
X-Powered-By: ASP.NET

tell me your token

[编辑]

如果您使用的是 Jwt Token ,您可以使用以下代码注册 JwtBearer Authentication :

services.AddAuthentication(options => {
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        ValidIssuer = Configuration["Jwt:Issuer"],
        ValidAudience = Configuration["Jwt:Audience"],
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
    };
});

[编辑2]

JwtBearer AuthenticationHandler 提供了一个 Challenge 来自定义 WWW-Authenticate :

    .AddJwtBearer(options => {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = Configuration["Jwt:Issuer"],
            ValidAudience = Configuration["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
        };
        options.Challenge ="tell me your token";;
    })

响应将是:

HTTP/1.1 401 Unauthorized
Server: Kestrel
WWW-Authenticate: tell me your token, error="invalid_token"
X-SourceFiles: =?UTF-8?B?RDpccmVwb3J0XDIwMThcMTBcMThcU08uYXV0aGVudGljYXRpb25TY2hlbWUsIE5vIERlZmF1bHRDaGFsbGVuZ2VTY2hlbWVcQXBwXEFwcFxhcGlcdmFsdWVzXDE=?=
X-Powered-By: ASP.NET
Content-Length: 0

注意 WwW-Authenticate header .

另一种方法是转发挑战

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options => {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = Configuration["Jwt:Issuer"],
            ValidAudience = Configuration["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
        };
        options.ForwardChallenge = "OurOwnAuthN";
    })
    .AddScheme<OurOwnAuthNOpts,OurOwnAuthNHandler>("OurOwnAuthN","Our Own Authentication Scheme",opts=>{
            // ...
     });

响应将是:

HTTP/1.1 401 Unauthorized
Transfer-Encoding: chunked
Server: Kestrel
X-SourceFiles: =?UTF-8?B?RDpccmVwb3J0XDIwMThcMTBcMThcU08uYXV0aGVudGljYXRpb25TY2hlbWUsIE5vIERlZmF1bHRDaGFsbGVuZ2VTY2hlbWVcQXBwXEFwcFxhcGlcdmFsdWVzXDE=?=
X-Powered-By: ASP.NET

tell me your token