.NET Core 2.2 JWT 令牌授权失败不返回 HTTP 401

Scr*_*ler 1 authentication http-status-code-401 jwt asp.net-core

我遇到了 .NET Core 如何在我的生产环境中处理 JSON Web 令牌 (JWT) 身份验证的问题。我正在使用 Postman 进行测试。如果我使用有效的令牌调用我的 API,它可以正常工作并从 API 端点返回预期的响应。但是,如果令牌已过期,我将不会收到预期的 HTTP 401。事实上,我没有收到任何响应,它只是悄悄地失败了。我的生产配置似乎不起作用。如果我在本地开发环境中对此进行测试,我不会遇到任何问题,并且过期的令牌会像预期的那样获得 401 未授权的 HTTP 响应。

我的 Web 应用程序对通过网站登录的人使用 cookie 身份验证,对连接到 API 的人使用 JWT 身份验证。这是 JWT 身份验证的配置。这是在 Startup.cs 中。这是 services.Authentication 的 JWT 配置部分。cookie 身份验证首先设置并且是默认身份验证方案。

.AddJwtBearer(options =>{
options.RequireHttpsMetadata = !_hostingEnvironment.IsDevelopment();

options.TokenValidationParameters = new TokenValidationParameters
{
    ValidateIssuer = true,
    ValidateAudience = true,
    ValidateLifetime = true,
    ValidateIssuerSigningKey = true,

    ValidIssuer = Settings.Api.JwtBearer.TokenValidation.ValidIssuer,
    ValidAudience = Settings.Api.JwtBearer.TokenValidation.ValidAudience,
    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Settings.Api.JwtBearer.TokenValidation.IssuerSigningKey))
};

options.Events = new JwtBearerEvents()
{
    OnAuthenticationFailed = context =>
    {
        context.Response.StatusCode = StatusCodes.Status401Unauthorized;
        context.Response.ContentType = "application/json; charset=utf-8";
        var message = _hostingEnvironment.IsDevelopment() ? context.Exception.ToString() : "An error occurred processing your authentication.";
        var result = JsonConvert.SerializeObject(new { message });
        return context.Response.WriteAsync(result);
    }
};});
Run Code Online (Sandbox Code Playgroud)

这是 Startup.cs 中的其他相关配置:

public void Configure(IApplicationBuilder app, IHostingEnvironment env){
if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/Error/Exception");

    app.UseStatusCodePagesWithReExecute("/Error/{0}");

    app.UseHsts();

    app.UseHttpsRedirection();
}

app.UseStaticFiles();

app.UseCors(Constants.Security.Cors.PolicyName);

app.UseAuthentication();

app.UseMvc(ConfigureRoutes);}
Run Code Online (Sandbox Code Playgroud)

` 我已经使用了很多配置设置并在生产中进行了测试,但是当令牌身份验证失败时,我总是最终没有响应。由于它在我的本地开发环境中运行良好,这一定意味着我的生产配置有问题,但我很困惑,没有想法。有关更多信息,请参阅错误日志中的错误示例:

信息:Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler[1] 未能验证令牌。Microsoft.IdentityModel.Tokens.SecurityTokenExpiredException:IDX10223:生命周期验证失败。令牌已过期。ValidTo: '[PII is hidden]', 当前时间: '[PII is hidden]'。在 Microsoft.IdentityModel.Tokens.Validators.ValidateLifetime(Nullable 1 notBefore, Nullable1 expires, SecurityToken securityToken, TokenValidationParameters validationParameters) 在 System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateLifetime(Nullable1 notBefore, Nullable1 过期, JwtSecurityToken jwtToken, TokenValidationParameters validationParameters) 在 System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateTokenPayload(JwtSecurityToken jwtToken, TokenValidationParameters validationParameters) 在 System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(Parameter token validationTokens)在 Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.d__6.MoveNext() 信息:Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler[7] Bearer 未经过身份验证。失败消息:IDX10223:生命周期验证失败。令牌已过期。ValidTo: '[PII is hidden]', 当前时间: '[PII is hidden]'。信息:Microsoft.AspNetCore.Authorization。DefaultAuthorizationService[2] 授权失败。信息:Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[3] 过滤器“Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter”处的请求授权失败。信息:Microsoft.AspNetCore.Mvc.ChallengeResult[1] 使用身份验证方案(Bearer)执行 ChallengeResult。信息:Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2] 在 109.4625 毫秒内执行操作 Web.Controllers.ApiController.Users (Web) 信息:Microsoft.AspNetCore.Routing.EndpointMiddleware[1] 执行端点'Web.Controllers.ApiController.用户 (Web)' 失败:Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware[1] 执行请求时发生未处理的异常。System.InvalidOperationException: 无法设置 StatusCode,因为响应已经开始。在 Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.set_StatusCode(Int32 value) 在 Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.Microsoft.AspNetCore.Http.Features.IHttpResponseFeature.set_StatusCode (Int32 值)在 Microsoft.AspNetCore.Http.Internal.DefaultHttpResponse.set_StatusCode(Int32 值)在 Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.d__7.MoveNext()

预先感谢您提供有关如何解决此问题的任何想法。

Scr*_*ler 5

所以我找到了一个部分解决方案,至少可以解除我的阻塞,这样我就可以继续我的 API 开发。尝试更改 JwtBearerEvents OnAuthenticationFailed 中的 context.Response 在我的生产环境中不起作用(尽管它在我的本地机器上运行良好)。我尝试了很多变体,但最后唯一有效的是从 Startup.cs 中删除整个代码块:

options.Events = new JwtBearerEvents()
{
    OnAuthenticationFailed = context =>
    {
        context.Response.StatusCode = StatusCodes.Status401Unauthorized;
        context.Response.ContentType = "application/json; charset=utf-8";
        var message = _hostingEnvironment.IsDevelopment() ? context.Exception.ToString() : "An error occurred processing your authentication.";
        var result = JsonConvert.SerializeObject(new { message });
        return context.Response.WriteAsync(result);
    }
};
Run Code Online (Sandbox Code Playgroud)

一旦我这样做了,在 Postman 中进行测试时,我开始获得 401 代码用于失败的令牌验证,这是一件好事和预期的行为。但是,在响应正文中,也返回了默认错误页面 HTML。

为了解决这个问题,我不得不修改 Startup.cs 中的 Configure 方法,以便 UseStatusCodePagesWithReExecute 忽略 API:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error/Exception");

        app.UseWhen(context => !context.Request.Path.Value.StartsWith("/api"), builder =>
        {
            builder.UseStatusCodePagesWithReExecute("/Error/{0}");
        });

        app.UseHsts();

        app.UseHttpsRedirection();
    }

    app.UseStaticFiles();

    app.UseCors(Constants.Security.Cors.PolicyName);

    app.UseAuthentication();

    app.UseMvc(ConfigureRoutes);
}
Run Code Online (Sandbox Code Playgroud)

这有效,但我不喜欢它。感觉像一个黑客。现在我得到 401 失败的令牌身份验证,并且正文中没有任何内容。就像我之前提到的那样,这解除了我的封锁,但我仍然希望通过响应返回自定义错误消息。

所以,我的下一个问题是,如果我不能使用 JwtBearerEvents OnAuthenticationFailed 添加消息,那么添加错误消息的正确位置在哪里?