首页 > 解决方案 > 有没有办法在 .Net 5 的 AuthorizationHandler 中重定向?

问题描述

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

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

在 .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=>痛苦的 Action 中插入这个基类方法。我想将逻辑保留在一个地方,而不必复制粘贴代码。

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

标签: authorization.net-5asp.net-authorizationredirecttoaction

解决方案


在 AuthorizationHandler 中没有找到任何好的重定向解决方案,所以我做了我能想到的第二好的事情,并创建了一个 TypeFilterAttribute 和一个用于在 AuthorizationHandler 之后存储重定向信息的服务。并在 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 中重定向的方式。


推荐阅读