Custom 401 and 403 response model with UseJwtBearerAuthentication middleware
当401和403出现时,我想用JSON响应模型进行响应。例如:
1 2 3 4 | HTTP 401 { "message":"Authentication failed. The request must include a valid and non-expired bearer token in the Authorization header." } |
我正在使用中间件(如该答案所建议)来拦截404,并且效果很好,但对于401或403而言并非如此。这是中间件:
1 2 3 4 5 6 7 8 9 | app.Use(async (context, next) => { await next(); if (context.Response.StatusCode == 401) { context.Response.ContentType ="application/json"; await context.Response.WriteAsync(JsonConvert.SerializeObject(UnauthorizedModel.Create(), SerializerSettings), Encoding.UTF8); } }); |
当放置在
在
Connection id"0HKT7SUBPLHEM": An unhandled exception was thrown by
the application. System.InvalidOperationException: Headers are
read-only, response has already started. at
Microsoft.AspNetCore.Server.Kestrel.Internal.Http.FrameHeaders.Microsoft.AspNetCore.Http.IHeaderDictionary.set_Item(String
key, StringValues value) at
Microsoft.AspNetCore.Http.Internal.DefaultHttpResponse.set_ContentType(String
value) at MyProject.Api.Startup.d.MoveNext() in
Startup.cs
设置是正确的,但是实际上不需要创建自己的中间件,因为您可以利用事件模型来覆盖默认的质询逻辑。
下面是一个示例,该示例将以纯文本形式返回包含OAuth2错误代码/说明的401响应(您当然可以返回JSON或所需的任何内容):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | app.UseJwtBearerAuthentication(new JwtBearerOptions { Authority ="http://localhost:54540/", Audience ="http://localhost:54540/", RequireHttpsMetadata = false, Events = new JwtBearerEvents { OnChallenge = async context => { // Override the response status code. context.Response.StatusCode = 401; // Emit the WWW-Authenticate header. context.Response.Headers.Append( HeaderNames.WWWAuthenticate, context.Options.Challenge); if (!string.IsNullOrEmpty(context.Error)) { await context.Response.WriteAsync(context.Error); } if (!string.IsNullOrEmpty(context.ErrorDescription)) { await context.Response.WriteAsync(context.ErrorDescription); } context.HandleResponse(); } } }); |
或者,您也可以使用状态代码页中间件,但是对于403响应,您不会对导致它的授权策略有任何提示:
1 2 3 4 5 6 7 8 9 | app.UseStatusCodePages(async context => { if (context.HttpContext.Request.Path.StartsWithSegments("/api") && (context.HttpContext.Response.StatusCode == 401 || context.HttpContext.Response.StatusCode == 403)) { await context.HttpContext.Response.WriteAsync("Unauthorized request"); } }); |
首先,中间件的顺序很重要。
Each middleware chooses whether to pass the request on to the next component in the pipeline, and can perform certain actions before and after the next component is invoked in the pipeline
UseJwtBearerAuthentication如果发生错误,将停止进一步的管道执行。
但是您的方法不适用于JwtBearerAuthentication中间件,因为当您遇到未经授权的错误时,中间件将发送WWWAuthenticate标头,这就是为什么您收到"响应已开始"异常的原因-请研究HandleUnauthorizedAsync方法。您可以重写此方法并实现自己的自定义逻辑。
另一种可能的解决方案(不确定是否可行)是在中间件中使用
当您写入httpContext.Response并调用next.Invoke(context)时,就会发生此问题,这是问题开始的地方:因为您已经启动了一个响应(导致Response.HasStarted = true),所以您不被允许来设置StatusCode了。
解决方案,请遵循以下代码:
1 2 3 4 5 6 7 | if (!context.Response.HasStarted) { try { await _next.Invoke(context); } } |
在将请求传递给下一个中间件之前检查HasStarted