为什么 `UseAuthentication` 必须放在 `UseRouting` 之后而不是之前?

Why does `UseAuthentication` have to be placed after `UseRouting` and not before?

根据documentation,中间件的顺序应该是这样的:

app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();

我有中间件来保护静态文件,基于this article(保护某些路由)。我遇到的问题是订单对我不起作用。如果用户已经获得授权,我只能保护文件夹。所以我需要在 UseStaticFiles 之前和 UseAuthentication 之后放置 UseProtectFolderUseAuthorization:

app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.UseProtectFolder(new ProtectFolderOptions
{
    Path = "/Secret",
    PolicyName = "Authenticated"
});
app.UseStaticFiles();

但这不会return任何静态文件。看起来 UseRouting 正在做一些使文件不可用的事情,returns 404,因为当我将顺序更改为此时,将 UseRouting 移到 UseStaticFiles 之后,它起作用了:

app.UseAuthentication();
app.UseAuthorization();

app.UseProtectFolder(new ProtectFolderOptions
{
    Path = "/Secret",
    PolicyName = "Authenticated"
});
app.UseStaticFiles();

app.UseRouting();

所以实际的顺序变化是UseAuthentication放在UseRouting之前(甚至在UseStaticFiles之前)。

来自文档:

The order that middleware components are added in the Startup.Configure method defines the order in which the middleware components are invoked on requests and the reverse order for the response. The order is critical for security, performance, and functionality.

我现在的问题是:在记录的顺序中,为什么 UseAuthentication 放在 UseRouting 之后?

是否有特殊原因或仅仅是出于性能原因?通过将 authentication/authorization 移到管道的较早位置,这是否会影响响应(逆序)?

发布这个问题后,我打开了一个关于 routing on github, and one about localization 的问题,希望获得更多信息。虽然不是所有问题都得到了直接的回答,但它帮助我找到了这个问题的答案。

看完comment of David Fowler:

UseAuthorization() -> Will look at the populated user and the current endpoint to determine if an authorization policy needs to be applied.

突然想到UseAuthorization没有问题。它适用于端点,因此我不需要它来保护文件夹。它还解释了为什么此语句仅在 UseEndpoints 语句之后才有意义。

为了全面了解我的配置,我有一个策略提供程序(包括策略)、一个 url 重写器(如 UseDefaultFiles)和保护某些文件夹的中间件。

我的结论是,我可以使用以下顺序,这与记录的顺序几乎相同:

// Identify the user. The only statement that is not in the order as documented
app.UseAuthentication();

// Middleware that adds policies
app.UsePolicyProvider();
// Protect the folder by policy
app.UseProtectFolder(new ProtectFolderOptions { Path = "/p", PolicyName = "admin" });
// URL rewriter for serving tenant specific files
app.UseTenantStaticFiles();

// Serve the static files
app.UseStaticFiles();

app.UseCookiePolicy();
app.UseCors();

app.UseRouting();
app.UseRequestLocalization();
app.UseAuthorization();
app.UseEndpoints();

关于订单的两条备注:

  1. UseRequestLocalization 仅在 UseRouting 之后有效
  2. 当涉及 URL 重写器(如 UseDefaultFiles)时,UseRouting 后 UseStaticFiles 不起作用。

我想在@Ruard 关于这个问题的 "Why UseRouting before UseAuth" 部分的回答中添加的一件事是摘自 Overview of ASP.NET Core authentication:

When using endpoint routing, the call to UseAuthentication must go:

  • After UseRouting, so that route information is available for authentication decisions.
  • Before UseEndpoints, so that users are authenticated before accessing the endpoints.

我仍然很好奇在调用 UseAuthentication() 之前需要哪些路由信息,所以我深入研究了源代码,发现信息 UseRouting() 必须对 UseAuthentication() 和 UseAuthorization 都可用() 就是 Endpoint class. Specifically, Endpoint.Metadata which is of type EndpointMetadataCollection.

EndpointMetadataCollection 只是一个对象数组,所以为了弄清楚那里实际填充了什么,我刚刚创建了一个空的 WebAPI 项目,在控制器上方设置了一个授权属性,加入了一些测试中间件,并添加了一个断点将 HttpContext.GetEndpoint().Metadata 分配给变量后。

事实证明,它正在填充的内容之一是关于我添加的授权属性的数据:

事后看来,这很有道理。在您甚至不知道端点是否需要授权(或者在我们知道请求需要身份验证之前用户是否已通过身份验证)之前就试图弄清楚请求是否已被授权是愚蠢的。

我偶然发现的其他真正有见地的东西是 this article by Areg Sarkissian,它真正深入到端点路由的本质,而不像微软文档那样枯燥。这个例子特别出色地展示了我上面提到的内容:

{
    if (env.IsDevelopment())
        app.UseDeveloperExceptionPage();
    else
        app.UseHsts();

    app.UseHttpsRedirection();

    app.UseRouting(routes =>
    {
        routes.MapControllers();

        //Mapped route that gets attached authorization metadata using the RequireAuthorization extension method.
        //This metadata will be added to the resolved endpoint for this route by the endpoint resolver
        //The app.UseAuthorization() middleware later in the pipeline will get the resolved endpoint
        //for the /secret route and use the authorization metadata attached to the endpoint
        routes.MapGet("/secret", context =>
        {
            return context.Response.WriteAsync("secret");
        }).RequireAuthorization(new AuthorizeAttribute(){ Roles = "admin" });
    });

    app.UseAuthentication();

    //the Authorization middleware check the resolved endpoint object
    //to see if it requires authorization. If it does as in the case of
    //the "/secret" route, then it will authorize the route, if it the user is in the admin role
    app.UseAuthorization();

    //the framework implicitly dispatches the endpoint at the end of the pipeline.
}