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

时间:2016-07-09 11:00:07

标签: asp.net-core asp.net-core-mvc 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."
}

我正在使用中间件(如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

3 个答案:

答案 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