MapWhen() 中的 MVC 授权应用于所有控制器

MVC authorization inside MapWhen() is applied to all controllers

在ASP.NetCore 3.0 Preview 7中,我试着写了一些代码如下:

public void Configure(IApplicationBuilder app) {
    app.MapWhen(context =>
        context.Request.Path.StartsWithSegments(
              new PathString("/UnsecureLog")),
        a => {
            a.UseRouting();
            a.UseEndpoints(endpoints => {
                endpoints.MapControllers();
            });
        }
    );
    
    app.UseAuthentication();
    app.UseAuthorization();

    app.MapWhen(context =>
        context.Request.Path.StartsWithSegments(
           new PathString("/SecureLog")),
        a => {
            a.UseRouting();
            a.UseEndpoints(endpoints => {
                endpoints.MapControllers()
                    .RequireAuthorization("MustBeReader");
            });
        }
    );
}

我的目标是允许在中间件中处理某些控制器而无需身份验证,我的想法是 MapWhen() 是实现这一目标的方法。

相反,我在点击 /UnsecureLog 端点时看到了这个错误:

System.InvalidOperationException: Endpoint ... contains authorization metadata,
but a middleware was not found that supports authorization.
Configure your application startup by adding app.UseAuthorization()
inside the call to Configure(..) in the application startup code.

翻译:“你如何为你不想保护的端点实施安全功能”。

我的结论是,在 any MapWhen() 块处理控制器逻辑中对 RequireAuthorization("MustBeReader") 的任何调用实际上将应用于 all MVC 控制器路由。

我目前的解决方法是删除第二个 MapWhen() 代码块中的 .RequireAuthorization("MustBeReader") 调用,并将其作为属性 ([RequireAuthorization("MustBeReader")]) 重新应用到我希望保护的那些端点。这可以正常工作并产生所需的行为。

但那不是目标,是吗?

我更愿意使用类似的策略来管理整个控制器组,同时让其他控制器完全不受安全保护,并在 Configure() 中处理所有这些。相反,我必须将所需的授权要求逐个应用到每个控制器。

我希望有更好的方法来实现路由,避免此处提到的问题。可能是我操作不当。

有想法吗?

我在 classes/controllers 上使用 Authorize 属性来强制系统要求经过身份验证的用户。如果您在此属性上指定参数,则需要用户拥有此声明。 对于无需身份验证即可访问的任何控制器或方法,我们在控制器或特定方法上设置 AllowAnonymous 属性。这应该否决任何授权要求。

将以下代码移到您的 Startup class 中更高的位置(app.MapWhen 上方)。

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

需要注意的是app.UseAuthorization必须出现在app.UseRouting()app.UseEndpoints(...)之间;

因此,它应该看起来像这样:

        app.UseRouting();
        app.UseAuthentication();
        app.UseAuthorization();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller}/{action=Index}/{id?}");
        });

今天我花了一些时间在这上面并发布了代码,我对自己的完整答案表示赞赏。感谢 mwilson 鼓励我再试一次。

在我的新方法之前,每次代码 运行 都会显示此警告...

Startup.cs(189,13): warning ASP0001: The call to UseAuthorization
should appear between app.UseRouting() and app.UseEndpoints(..)
for authorization to be correctly evaluated.

所以解决方案不仅仅是识别警告并注意它,还要找到一种方法来安抚编译器之神。 (你会在下面看到我还没有想出如何做到这一点。)

这是我今天弄明白的。我将 UseAuthenticationUseAuthorization 调用放在两个不同的地方。这行得通。有点。

在这个 API 项目中,我现在能够 运行 匿名不安全的端点、安全的端点,并且为了加分,还可以使用安全的 GraphQL 端点。

FWIW,代码如下:

        public void Configure(...)
        {
            ...
            ...
            app.UseStaticFiles();
            app.UseRouting();

            app.MapWhen(
                context => (context.Request.Path
                    .StartsWithSegments(new PathString("/api"))
                ),
                a =>
                {
                    a.UseRouting();
                    a.UseAuthentication();
                    a.UseAuthorization();
                    a.UseEndpoints(endpoints =>
                    {
                        endpoints
                            .MapControllers()
                            .RequireAuthorization(...);
                    });
                }
            );

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

            app.UseWebSockets();
            app.UseGraphQLWebSockets<...>(...);
            app.UseGraphQL<...>(...);
        }

可行,但我仍然收到编译器警告。更重要的是,如果我太聪明了一半并尝试使用以下控制器...


    [Route("vapi/[controller]")]
    //[AllowAnonymous]
    public class VersionController : Controller
    { ...

连同这个额外的 Startup.cs 代码...

            app.MapWhen(
                context => (context.Request.Path
                    .StartsWithSegments(new PathString("/vapi"))
                ),
                a =>
                {
                    a.UseRouting();
                    // a.UseAuthentication(); <-- look, Ma, no authentication!
                    // a.UseAuthorization(); <-- look, Ma, no authorization!
                    a.UseEndpoints(endpoints =>
                    {
                        endpoints
                            .MapControllers()
                            .RequireAuthorization(...);
                    });
                }
            );

我也仍然收到我的 OP 中指出的错误。 (这一切又回到了我的脑海里,就像一个古老的噩梦......)

Endpoint ....Controllers.VersionController.Version (...) contains authorization
metadata, but a middleware was not found that supports authorization.
Configure your application startup by adding app.UseAuthorization() ...

所以我要离开的地方是这样的:我仍然不能做我想做的事情,那就是只保护某些控制器路径。我可以做的(不添加 "vapi"-路径支持代码启动)是这样的:

    [Route("api/[controller]")]
    [AllowAnonymous]
    public class VersionController : Controller
    { ...

所以...我会称之为答案,直到有人发布了一组可用的代码来改进它。