【问题标题】:ASP.NET Core 3.0 Policy RedirectASP.NET Core 3.0 策略重定向
【发布时间】:2020-03-16 22:32:45
【问题描述】:

我们拥有控制某些服务高级页面的这部分代码。 On 方法 Deny 重定向到升级页面,供没有高级会员资格的人使用。

代码在 asp.NET core 2 上运行完美,但在 asp.NET core 3 上运行失败。 context.Resource 不再是 AuthorizationFilterContext 类型,但 Endpoint 没有提供 Result 成员。

¿如何使用 asp.Net core 3 上提供的新 Enpoint 进行页面重定向?

public Task Deny(AuthorizationHandlerContext context, SubscriptionRequirement requirement)
    {
        var mvcContext = context.Resource as AuthorizationFilterContext;
        if (mvcContext == null)
            return Task.CompletedTask;

        mvcContext.Result = new RedirectToActionResult("Upgrade", "Subscription", new { ReturnUrl = _contextAccessor.HttpContext.Request.Path });
        context.Succeed(requirement);
        return Task.CompletedTask;
    }

【问题讨论】:

    标签: c# asp.net-mvc-3 asp.net-core


    【解决方案1】:

    根据 SOC 负责人的说法,将授权与响应重定向混为一谈似乎不是一个好习惯。

    相反,您可以将授权逻辑包装到策略中,然后调用 IAuthorizationService 并随时随地重定向。

    假设您定义了“高级会员”政策。然后您可以毫不费力地使用中间件/资源过滤器/操作过滤器甚至操作方法重定向请求。例如,我创建MembershipResourceFilter 如下:

    public class MembershipResourceFilter : IAsyncResourceFilter
    {
        public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
        {
            var HttpContext = context.HttpContext;
            var authZ = HttpContext.RequestServices.GetRequiredService<IAuthorizationService>();
            var routeData= context.RouteData;
            var result = await authZ.AuthorizeAsync(HttpContext.User, routeData,"premium membership");
            if(!result.Succeeded)
            {
                context.Result = new RedirectToActionResult("Upgrade", "Subscription", new { ReturnUrl = HttpContext.Request.Path });
            }
            await next();
        }
    }
    

    我使用以下策略测试上述代码,它对我来说很好。

    services.AddAuthorization(o =>{
        o.AddPolicy("premium membership", pb => pb
            .RequireAuthenticatedUser()
            .RequireAssertion((context)=>{
                // check current context.User has premium membership
                var user = context.User;
                var routeData = context.Resource as RouteData;
                if(routeData != null){
                    try{
                        var controller = routeData.Values["controller"]?.ToString();
                        var action = routeData.Values["action"]?.ToString();
                        // now you get the route value
                        if(controller == "Home" && action == "Action"){
                            // ...
                            return true;
                        }
                    }catch{
                        return false;
                    }
                }
                return false;
            })
        );
    });
    

    [编辑]

    如果您不想更改[Authorize("Premium")],您可以创建一个简单的中间件,而不是资源过滤器:

        ...
        app.UseAuthentication();
        app.UseRouting();
    
    
        app.Use(async(ctx,next)=>{
            var ep= ctx.Features.Get<IEndpointFeature>()?.Endpoint;
            var authAttr = ep?.Metadata?.GetMetadata<AuthorizeAttribute>()
            if(authAttr!=null && authAttr.Policy == "premium membership"){
                var authService = ctx.RequestServices.GetRequiredService<IAuthorizationService>();
                var result = await authService.AuthorizeAsync(ctx.User, ctx.GetRouteData(),authAttr.Policy);
                if(!result.Succeeded)
                {
                    var path = $"/Subscription/Upgrade?ReturnUrl={ctx.Request.Path}";
                    ctx.Response.Redirect(path) ;
                    return;
                }
            }
            await next();
        });
    
        app.UseAuthorization();
        app.UseEndpoints(endpoints =>{ ... });
    

    中间件和资源过滤器基本上做同样的事情:调用授权服务并在需要时重定向。

    【讨论】:

    • 您的解决方案是用此过滤器装饰需要会员资格的控制器,谁需要“高级会员”政策?控制器已经用例如 [Authorize("Premium")] 装饰,我不想更改所有控制器。我只是要求像我在 2.2 上那样在策略中重定向的方式
    • @JuanRedondo 查看我的更新答案以避免更改[Authorize("Premium")]
    【解决方案2】:

    我通过将IHttpContextAccessor 注入AuthorizationHandler 构造函数然后这样做来解决这个问题:

    public Task Deny(AuthorizationHandlerContext context, SubscriptionRequirement requirement)
    {
        //your logic here
        context.Succeed(requirement);
        return _contextAccessor.HttpContext.ExecuteResultAsync(
          new RedirectToActionResult("Upgrade", "Subscription", new { 
            ReturnUrl = _contextAccessor.HttpContext.Request.Path 
        }));
    }
    

    HttpContext.ExecuteResultAsync是一个扩展方法:

     public static class HttpContextExtensions  
    {
        private static readonly RouteData EmptyRouteData = new RouteData();
    
        private static readonly ActionDescriptor EmptyActionDescriptor = new ActionDescriptor();
    
        public static Task ExecuteResultAsync<TResult>(this HttpContext context, TResult result)
            where TResult : IActionResult
        {
            if (context == null) throw new ArgumentNullException(nameof(context));
            if (result == null) throw new ArgumentNullException(nameof(result));
    
            var executor = context.RequestServices.GetRequiredService<IActionResultExecutor<TResult>>();
    
            var routeData = context.GetRouteData() ?? EmptyRouteData;
            var actionContext = new ActionContext(context, routeData, EmptyActionDescriptor);
    
            return executor.ExecuteAsync(actionContext, result);
        }
    }
    

    我正在使用核心 3.1,以防它有所作为。

    【讨论】:

    • 找不到“Liftshare.Web.Extensions”,能否提供该 Nuget 包的链接?
    • 调用 context.Succeed(requirement) 意味着它将调用装饰它的操作方法,然后重定向到升级/订阅。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-15
    • 1970-01-01
    • 1970-01-01
    • 2019-05-05
    • 2020-07-09
    • 1970-01-01
    相关资源
    最近更新 更多