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

Posted

技术标签:

【中文标题】没有指定 authenticationScheme,也没有找到 DefaultChallengeScheme - ASP.NET core 2.1【英文标题】:No authenticationScheme was specified, and there was no DefaultChallengeScheme found - ASP.NET core 2.1 【发布时间】:2019-03-22 02:00:58 【问题描述】:

我正在使用 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);
    
  

如果我为授权方法传递标头,它工作正常,但是缺少相同的标头会产生错误No authenticationScheme was specified, and there was no DefaultChallengeScheme found.

这里我有两个问题:

1) 如果没有指定标头,可以向用户端发送 500 异常吗?

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

【问题讨论】:

【参考方案1】:

如果没有指定header,是否可以向用户端发送500异常?

恐怕这不是个好主意。

500 状态代码表示存在服务器错误。当客户端发送没有令牌的请求时,告诉客户端“发生内部错误”是没有意义的。更好的方法是发送401挑战用户或发送403禁止。

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

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

正如错误描述的那样,抛出错误是因为没有指定AuthenticationScheme,也没有找到DefaultChallengeScheme

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

否则,如果您想自定义验证用户的方式(例如,自定义基于令牌的身份验证),您应该创建一个新的令牌身份验证处理程序。已经有一个内置的抽象 AuthenticationHandler 类:

public abstract class AuthenticationHandler<TOptions> : IAuthenticationHandler 
    where TOptions : AuthenticationSchemeOptions, new()

     // ...

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

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 标头。

另一种方法是通过以下方式转发挑战

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

【讨论】:

注册services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme); 并添加[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] 但是,得到"No authentication handlers are registered. Did you forget to call AddAuthentication().Add[SomeAuthHandler](\"Bearer\",...)? 的错误 @Div 您不需要存储令牌。 Jwt Token 存储在客户端并在服务器上验证。只需创建一个新令牌并将它们发送给客户端就可以了。 @Div 我不知道是否有标准的方法来做到这一点。但我曾经同时发出一个accessToken(2小时内过期)和一个refreshToken(2.1小时内过期)。客户端使用 refreshToken 请求新令牌,将旧的 accessToken 和 refreshToken 替换为新的。 @Div 即使刷新令牌在某个时间过期,客户端也可以再次登录并获得新的 accessToken 和刷新令牌。服务器不关心那个。他们只是验证令牌。 @Div 对不起,我误解了你关于 refresh token 的问题。我以为您正在尝试保存 accessToken 。人们通常将刷新令牌保存在某处。如果您不想保存刷新令牌,只需发出一个名为 refresh token 的新 JwtToken ,它被视为刷新令牌,只能用作刷新令牌。

以上是关于没有指定 authenticationScheme,也没有找到 DefaultChallengeScheme - ASP.NET core 2.1的主要内容,如果未能解决你的问题,请参考以下文章

未指定 authenticationScheme,并且没有找到具有默认身份验证和自定义授权的 DefaultChallengeScheme

简单的web服务器

为啥我应该始终为我的 Authorize 属性定义 JwtBearerDefaults.AuthenticationScheme?

添加授权时缺少身份验证方案

asp.net核心未绑定当前用户

http 异步 接收 回传 数据文字和文件流