我希望在401和403发生时使用JSON响应模型进行响应。例如:
HTTP 401
{
"message": "Authentication failed. The request must include a valid and non-expired bearer token in the Authorization header."
}
我正在使用中间件(如this answer中所建议的)拦截404并且它工作得很好,但401或403并非如此。这是中间件:
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);
}
});
当app.UseJwtBearerAuthentication(..)
置于Startup.Configure(..)
以下时,它似乎完全被忽略,并返回正常的401.
当在app.UseJwtBearerAuthentication(..)
中放置Startup.Configure(..)
时,会抛出以下异常:
连接ID“0HKT7SUBPLHEM”:抛出了未处理的异常 应用程序。 System.InvalidOperationException:标题是 只读,响应已经开始。在 Microsoft.AspNetCore.Server.Kestrel.Internal.Http.FrameHeaders.Microsoft.AspNetCore.Http.IHeaderDictionary.set_Item(字符串 key,StringValues value)at Microsoft.AspNetCore.Http.Internal.DefaultHttpResponse.set_ContentType(字符串 值)在MyProject.Api.Startup。< b__12_0> d.MoveNext()in Startup.cs
答案 0 :(得分:8)
Set处于正确的轨道上,但实际上并不需要创建自己的中间件,因为您可以利用事件模型来覆盖默认的挑战逻辑。
这是一个示例,它将返回包含OAuth2错误代码/描述的401响应作为纯文本(您当然可以返回JSON或任何您想要的内容):
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响应,您不会对导致它的授权策略有任何暗示:
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");
}
});
答案 1 :(得分:1)
首先,middlewares的顺序很重要。
每个中间件选择是否将请求传递给管道中的下一个组件,并且可以在管道中调用下一个组件之前和之后执行某些操作
如果发生错误,UseJwtBearerAuthentication会停止进一步的管道执行。
但是你的方法不适用于JwtBearerAuthentication中间件,因为当你有未经授权的错误时,中间件会发送WWWAuthenticate标头,这就是为什么你得到“响应已经开始”的例外 - 请查看HandleUnauthorizedAsync方法。您可以覆盖此方法并实现自己的自定义逻辑。
另一种可能的解决方案(不确定是否有效)是在中间件中使用HttpContext.Response.OnStarting
回调,因为它在标头发送之前被调用。你仔细看看SO answer
答案 2 :(得分:0)
当您写入httpContext.Response并调用next.Invoke(context)时,就会发生这种情况,这是问题开始的地方:因为您已经启动了响应(导致Response.HasStarted = true),所以不允许您来设置StatusCode了。 解决方案,请遵循以下代码:
if (!context.Response.HasStarted)
{
try
{
await _next.Invoke(context);
}
}
在将请求传递到下一个中间件之前检查HasStarted