使用UseJwtBearerAuthentication中间件的自定义401和403响应模型

Dav*_*New 10 asp.net-core-mvc asp.net-core asp.net-core-1.0

当401和403出现时,我想用JSON响应模型做出响应.例如:

HTTP 401
{
  "message": "Authentication failed. The request must include a valid and non-expired bearer token in the Authorization header."
}
Run Code Online (Sandbox Code Playgroud)

我正在使用中间件(如本答案中所建议的)拦截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);
    }
});
Run Code Online (Sandbox Code Playgroud)

当置于下方app.UseJwtBearerAuthentication(..)Startup.Configure(..),它似乎被完全忽略并返回正常的401.

当置于ABOVE app.UseJwtBearerAuthentication(..)中时Startup.Configure(..),将抛出以下异常:

连接标识"0HKT7SUBPLHEM":应用程序抛出了未处理的异常.System.InvalidOperationException:标头是只读的,响应已经开始.在MicrosoftPro.AspNetCore.Server.Kestrel.Internal.Http.FrameHeaders.Microsoft.AspNetCore.Http.IHeaderDictionary.set_Item(String key,StringValues value),位于MyProject的Microsoft.AspNetCore.Http.Internal.DefaultHttpResponse.set_ContentType(String value). Startup.cs中的Api.Startup.<b__12_0> d.MoveNext()

Kév*_*let 9

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();
        }
    }
});
Run Code Online (Sandbox Code Playgroud)

或者,您也可以使用状态代码页中间件,但对于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");
    }
});
Run Code Online (Sandbox Code Playgroud)


Set*_*Set 1

首先,中间件的顺序很重要。

每个中间件选择是否将请求传递给管道中的下一个组件,并且可以在调用管道中的下一个组件之前和之后执行某些操作

如果发生错误,UseJwtBearerAuthentication 将停止进一步的管道执行。

但是您的方法不适用于 JwtBearerAuthentication 中间件,因为当您出现未经授权的错误时,中间件会发送 WWWAuthenticate 标头,这就是为什么您会收到“响应已开始”异常的原因 - 查看HandleUnauthorizedAsync方法。您可以重写此方法并实现您自己的自定义逻辑。

HttpContext.Response.OnStarting另一种可能的解决方案(不确定是否有效)是在中间件中使用回调,因为它是在标头发送之前调用的。你可以看看这个答案