有没有办法在 .Net 5 的 AuthorizationHandler 中重定向?

Is there a way to Redirect in AuthorizationHandler in .Net 5?

如果某些检查失败,我想重定向到 AuthorizationFilter 中的操作。我想重定向而不是将用户发送到“AccessDenied”视图的原因是为了防止用户知道端点在某些条件下存在。

一个更好的例子是当您创建一个登录系统时,如果 username/email 或密码错误,您“不应该”明确地告诉用户。如果有人告诉他们 username/email 是正确的,他们就知道它存储在数据库中,现在他们只需要找出密码即可。

在 .Net Core 3.0 之前的 AuthorizationHandler 内部重定向时,我可以使用:

if (context.Resource is AuthorizationFilterContext redirectContext) 
{
    redirectContext.Result = new RedirectResult("/Account/NoPageHere");
    context.Succeed(requirement); 
}

但从 .Net Core 3.0+ 开始,这已更改且不再受支持。所以我的问题是,一个可以从 AuthorizationHandler 重定向还是完全删除?

我可以使用的解决方案是通过 AuthorizationHandler 中的 HttpContextAccessor 将一些数据存储在 HttpContext.Items 中,然后根据该数据进行重定向。如果我的控制器继承自一个基本控制器,该控制器具有处理 HttpContext.Items 内的数据并决定我们是否应该重定向以及重定向到何处的方法,那么这会有些好用。唯一的问题是我需要在每个使用 AuthorizationFilter => pain 的 Action 中插入这个基本 class 方法。我想将逻辑放在一个地方,而不必到处复制粘贴代码。

如果有人有更好的建议,我很乐意听取他们的意见!

没有找到在 AuthorizationHandler 内部重定向的任何好的解决方案,所以我做了我能想到的第二好的事情,并在 AuthorizationHandler 之后创建了一个 TypeFilterAttribute 和一个用于存储重定向信息的服务。并在 ActionMethod 触发之前重定向。

我们的想法是在 AuthorizationHandler 中进行所有验证检查,但问题是我们必须始终验证请求。这并不理想,但根据我的知识,如果不将请求设置为 Succeed(),我们将无法重定向。当我们在 AuthroizationHandler 中遇到一个或多个失败条件时,我们将重定向信息存储在 RedirectService 中,我们将在 TypeFilterAttribute 中再次使用它来重定向到正确的方法。您可以通过返回来提前停止 AuthorizationHandler,或者像我的示例一样继续添加具有优先级 ID 的潜在多重重定向。

TypeFilterAttribute 属于 IActionFilter 类型,并且具有将 运行 在 ActionMethod 之前和之后的方法。为了我的需要,我必须在 ActionMethod 之前检查,但如果需要,可以在任何其他点重定向。

根据我自己的代码添加了下面的代码示例,但出于安全原因进行了更改,但希望它能说明问题。

授权处理程序

protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, MembershipRequirement requirement)
{  
    // We preemptively set authorization as successful as we need to succeed to redirect.
    context.Succeed(requirement);
    
    var id = _service.GetId();
    if (id == 0)
    {
        // Prio Id is used in case multiple redirects are set to find the most important ( 1 => most important )
        _redirectService.AddRedirect(1, MVC.Home.Error("404"));
    }

    var name = _service.getName();
    if (name.toLower() != "cool")
    {
        // Prio Id is used in case multiple redirects are set to find the most important ( 1 => most important )
        _redirectService.AddRedirect(2, MVC.Account.ChangeName());
    }
}

重定向服务

public class RedirectService : IRedirectService
{
    public bool IsRedirecting { get; set; }
    public SortedDictionary<int, IActionResult> RedirectResults { get; set; } = new SortedDictionary<int, IActionResult>();
    
    // Ignores value if order ( key ) is already set
    public void AddRedirect(int order, IActionResult redirectResult)
    {
        if (redirectResult == null || RedirectResults.ContainsKey(order)) return;
        
        IsRedirecting = true;
        RedirectResults.Add(order, redirectResult);
    }
}

public interface IRedirectService
{
    public bool IsRedirecting { get; set; }
    public SortedDictionary<int, IActionResult> RedirectResults { get; set; }

    public void AddRedirect(int order, IActionResult redirectResult);
}

重定向类型过滤器属性:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class RedirectFilterAttribute : TypeFilterAttribute
{
    public RedirectFilterAttribute() : base(typeof(RedirectFilterType)) {}

    private class RedirectFilterType : IActionFilter
    {
        private readonly IRedirectService _redirectService;
    
        public RedirectFilterType(IRedirectService redirectService)
        {
            _redirectService = redirectService;
        }

        public void OnActionExecuting(ActionExecutingContext context)
        {
            // Check if we should redirect
            if (!_redirectService.IsRedirecting) return;
        
            var callInfo = _redirectService.RedirectResults.First().Value.GetR4ActionResult();
            context.Result = new RedirectToRouteResult(null, callInfo.RouteValueDictionary, null);
        }

        public void OnActionExecuted(ActionExecutedContext context) {}
    }
}

Startup.cs依赖注入

// Setup RedirectService
services.AddScoped<IRedirectService, RedirectService>();

// Setup AuthroiztionHandler(s)
services.AddScoped<IAuthorizationHandler, MembershipAuthorizationHandler>();

config.AddPolicy(nameof(MembershipRequirement.MembershipPolicy),
    policy => policy.Requirements.Add(new MembershipRequirement(isAdmin: false)));

config.AddPolicy(nameof(MembershipRequirement.AdminMembershipPolicy),
    policy => policy.Requirements.Add(new MembershipRequirement(isAdmin: true)));

PS:我正在使用 R4MVC(这太棒了)所以您需要更改在 TypeFilterAttribute 中重定向的方式。

有更好的解决方案。

使用IHttpContextAccessor获取当前HttpContext然后调用HttpContext.Response.Redirect。不需要任何特殊的东西,也不是 net5.0 特定的。