如何在AuthorizationOptions中设置FallbackPolicy后针对不正确的URL返回HttpStatusCode 404?

3
在我目前正在开发的Web应用程序中,所有用户都需要进行身份验证。目前使用AuthorizeFilter来处理此需求。
现在我需要能够将不同的授权策略应用于应用程序的不同部分,因此我想从使用全局授权过滤器切换到设置后备策略(如官方文档所述,并且建议这样做)。
这个方法可以达到预期的效果,但是如果请求不存在的资源,则返回HttpStatusCode 401(如果未经身份验证),或者403(如果已经通过身份验证但某些其他要求没有满足——在默认/后备策略中有几个)。以前,使用授权过滤器解决方案时,会返回404。我猜测原因是后备策略在管道中比授权过滤器更早地进行了评估,但这仍然是一个我想避免的副作用。
我该如何在使用 FallbackPolicy 的同时让应用程序像以前一样返回 404?如果应用程序正在使用 net5.0(或更高版本),我想我可以使用自定义 IAuthorizationMiddlewareResultHandler,但升级不是短期计划中的事情,这意味着解决方案必须适用于 netcoreapp3.1

请查看MSDN上的Roles Blob: https://learn.microsoft.com/en-us/aspnet/web-forms/overview/older-versions-security/roles/creating-and-managing-roles-cs?force_isolation=true - jdweng
@jdweng 我不明白那与问题有何关系?这个问题与使用基于角色的授权无关。实际上,该应用程序中没有使用角色。 - jfiskvik
也许这个链接可以帮到你:https://learn.microsoft.com/zh-cn/aspnet/core/security/authorization/simple?view=aspnetcore-3.1 - GH DevOps
除了FallBackPolicy之外,还有DefaultPolicy,它始终需要经过身份验证的用户(默认情况下)。您可以在UseEndpoints构建器委托中添加endpoints.MapControllers().RequireAuthorization();,并强制对所有端点使用AuthorizeFilter,然后您可以在其上添加自己的策略。为了继续使用FallBackPolicy并返回404,我在下面提供了一个建议的解决方案。 - GPuri
3个回答

1

使用回退策略返回404:

app.UseEndpoints(endpoints =>
{
   endpoints.MapControllers();
   endpoints.MapRazorPages();
   endpoints.MapFallbackToController("api/{**slug}", nameof(ErrorController.Error404), "Error");
   endpoints.MapFallbackToPage("{**slug}", "/Public/Errors/404");
});

显然,被AllowAnonymous修饰的操作

    [Route("api/[controller]")]
    [ApiController]
    public class ErrorController : ControllerBase
    {
        [HttpGet]
        [AllowAnonymous]
        public IActionResult Error404()
        {
            return NotFound();
        }
    }

对于Razor Pages:

builder.AddRazorPagesOptions(options =>
{
  options.Conventions.AllowAnonymousToFolder("/Public");

政策
options.FallbackPolicy = new AuthorizationPolicyBuilder()
                    .RequireAuthenticatedUser()
                    .RequireRole(LoginEntities.Sede.ToString())
                    .Build();

options.DefaultPolicy = new AuthorizationPolicyBuilder()
                    .RequireAuthenticatedUser()
                    .Build();

enter image description here


这并没有回答问题。我不是试图让404错误回退到另一个页面。我试图在使用授权回退策略(而不是指定所有用户必须经过身份验证的全局授权策略)时,使对不存在资源的请求返回404状态码,而不是401状态码。 - jfiskvik
在你发表评论之前,你测试过我的答案了吗?我已经更新了证据;-) - Juan R
是的,您的建议确实解决了我主要的问题,即获得401状态代码而不是404状态代码。另外,我希望它能像以前一样,只返回错误代码,没有正文等(这一点我在我的问题中没有清楚地说明)。您的建议中可以通过错误控制器中的自定义响应来实现这一点,但如果可能的话,我更喜欢不必创建额外的控制器。无论如何,上面的评论措辞不当,这是我的错。 - jfiskvik

1

我使用 netcoreapp3.1 并添加了一个自定义授权处理程序,该处理程序扩展了 DenyAnonymousAuthorizationRequirement,最终达到了我想要的结果。

public class CustomDenyAnonymousAuthorizationRequirement : DenyAnonymousAuthorizationRequirement
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, DenyAnonymousAuthorizationRequirement requirement)
    {
        if (context.Resource != null)
        {
            return base.HandleRequirementAsync(context, requirement);
        }
        
        context.Succeed(requirement);
        return Task.CompletedTask;
    }
}

如果没有资源,此代码将标记需求已经得到满足。

像以下方式将其添加到服务集合中:

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddSingleton<IAuthorizationHandler, CustomDenyAnonymousAuthorizationRequirement>();
}

如果使用net5.0+,可以使用自定义的IAuthorizationMiddlewareResultHandler来实现相同的功能:
public class CustomAuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler
{
    private readonly AuthorizationMiddlewareResultHandler _defaultHandler = new();

    public async Task HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult)
    {
        if ((authorizeResult.Challenged || authorizeResult.Forbidden) && context.GetEndpoint() == null)
        {
            context.Response.StatusCode = (int)HttpStatusCode.NotFound;
            return;
        }

        await _defaultHandler.HandleAsync(next, context, policy, authorizeResult);
    }
}

并添加到服务集合中:

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddSingleton<IAuthorizationMiddlewareResultHandler, CustomAuthorizationMiddlewareResultHandler>();
}

1
@jeancallisti:我可以在重写HandelRequirementAsync方法时返回Task.CompledtedTask,因为我没有等待任何操作,因此没有将其标记为async。我要么从基本方法返回任务,要么返回一个完成的任务,调用者可以在我的方法覆盖中等待它。 - jfiskvik
这是我评论的更新版本,考虑了您的澄清:对于任何需要在HandleRequirementAsync内部使用await的人,只需添加async关键字,然后简单地执行return;而不是return Task.CompletedTask; - jeancallisti

0

为解决应用 FallBackPolicy 后不再收到 404 的问题,我们可以使用中间件。

 public class TestMiddleware
    {
        private readonly RequestDelegate next;

        public TestMiddleware(RequestDelegate requestDelegate)
        {
            next = requestDelegate;
        }

        public async Task Invoke(HttpContext httpContext)
        {
            await next(httpContext);
            var ep = httpContext.GetEndpoint();
            if(ep == null)
            {
                httpContext.Response.StatusCode = 404;
                await httpContext.Response.WriteAsync("Not Found!!!");
            }
        }
    }

以上只是这种中间件的一个示例实现。我们可以在管道中 UseEndpoints 中间件之后添加此中间件。
 app.UseEndpoints(endpoints =>
                {
                 endpoints.MapControllers();
                });
app.UseMiddleware<TestMiddleware>();

在中间件代码中,我们使用HttContext上的GetEndPoints方法来了解调用是否实际映射到任何ActionMethod,如果没有,我们可以返回404。因此,我们可以拥有回退策略,同时对于未找到的资源可以返回404。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接