ASP.NET 5 授权两个或多个策略(或组合策略)

Posted

技术标签:

【中文标题】ASP.NET 5 授权两个或多个策略(或组合策略)【英文标题】:ASP.NET 5 Authorize against two or more policies (OR-combined policy) 【发布时间】:2016-06-07 04:25:31 【问题描述】:

是否可以针对两个或多个策略应用授权?我正在使用 ASP.NET 5,rc1。

[Authorize(Policy = "Limited,Full")]
public class FooBarController : Controller

    // This code doesn't work

如果没有,我如何在不使用策略的情况下实现这一目标?有两组用户可以访问此控制器:“完全”和“有限”。用户可能属于“完全”或“有限”,或两者兼而有之。他们只需要属于这两个组之一即可访问此控制器。

【问题讨论】:

【参考方案1】:

不是你想要的方式;政策旨在累积。例如,如果您使用两个单独的属性,那么它们必须都通过。

您必须在单个策略中评估 OR 条件。 但是您不必在单个处理程序中将其编码为 OR。您可以有一个具有多个处理程序的需求。如果任一处理程序标记成功,则满足要求。请参阅我的Authorization Workshop 中的第 6 步。

【讨论】:

如果策略是累积的,为什么在使用自定义策略时会替换默认值?这个问题的核心来自this question。我正在声明自定义策略,并且不希望未经身份验证的请求进入我的授权处理程序。我目前使用的方法是从您的授权研讨会的第 2 步开始(授权所有端点并将[AllowAnonymous] 放在需要的地方)。感觉像是一种反模式,但我可能很愚蠢! 基本上我们假设如果您正在制定自己的政策,那么您就知道自己在做什么。应用策略表示您将覆盖默认值。 明白。只是感觉默认策略应该是一个“基线”,就好像它是您在一组自定义策略中的第一个策略一样。 是的,与其说是默认设置,不如说是“如果没有指定任何内容,请执行此操作。” @steamrolla,它们是累积的,但 ASP 网络授权使用 Lest 特权方法来处理安全性,它们都必须通过,在你的情况下,[AllowAnonymous] 通过但可能被以下策略阻止。 【参考方案2】:

一旦设置了新政策“LimitedOrFull”(假设它们与声明类型名称匹配),就会创建如下要求:

options.AddPolicy("LimitedOrFull", policy =>
    policy.RequireAssertion(context =>
        context.User.HasClaim(c =>
            (c.Type == "Limited" ||
             c.Type == "Full"))));

https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-2.1#using-a-func-to-fulfill-a-policy

【讨论】:

【参考方案3】:

Net Core 可以选择拥有多个具有相同 AuthorizationRequirement 类型的 AuthorizationHandler。只有其中之一必须成功通过授权 https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-2.1#why-would-i-want-multiple-handlers-for-a-requirement

【讨论】:

options.AddPolicy("ElevatedRights", policy => policy.RequireRole("Administrator", "PowerUser", "BackupAdministrator"));【参考方案4】:

我使用策略和角色:

[Authorize(Policy = "ManagerRights", Roles = "Administrator")]

【讨论】:

【参考方案5】:

使用按需动态创建需求的解决方案最适合我:

    创建单独的“有限”和“完整”策略要求的接口:
    public interface ILimitedRequirement : IAuthorizationRequirement  
    public interface IFullRequirement : IAuthorizationRequirement  
    为授权创建自定义属性:
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
    public class AuthorizeAnyAttribute : AuthorizeAttribute 

        public string[] Policies  get; 

        public AuthorizeAnyAttribute(params string[] policies) : base(String.Join("Or", policies))
            => Policies = policies;
    
    ILimitedRequirementIFullRequirement 创建授权处理程序(请注意,这些处理程序处理接口,而不是类):
    public class LimitedRequirementHandler : AuthorizationHandler<ILimitedRequirement> 

        protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ILimitedRequirement requirement) 
            if(limited)
                context.Succeed(requirement);
            
        
    

    public class FullRequirementHandler : AuthorizationHandler<IFullRequirement> 

        protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, IFullRequirement requirement) 
            if(full)
                context.Succeed(requirement);
            
        
    
    如果您的授权处理程序很重(例如,其中一个访问数据库)并且您不希望其中一个执行授权检查,如果另一个已经成功或失败,您可以使用下一个解决方法(记住该顺序处理程序注册的数量直接决定了它们在请求管道中的执行顺序):
    public static class AuthorizationExtensions 

        public static bool IsAlreadyDetermined<TRequirement>(this AuthorizationHandlerContext context)
            where TRequirement : IAuthorizationRequirement
            => context.HasFailed || context.HasSucceeded
                || !context.PendingRequirements.Any(x => x is TRequirement);

    


    public class LimitedRequirementHandler : AuthorizationHandler<ILimitedRequirement> 

        protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ILimitedRequirement requirement) 
            if(context.IsAlreadyDetermined<ILimitedRequirement>())
                return;

            if(limited)
                context.Succeed(requirement);
            
        
    

    public class FullRequirementHandler : AuthorizationHandler<IFullRequirement> 

        protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, IFullRequirement requirement) 
            if(context.IsAlreadyDetermined<IFullRequirement>())
                return;

            if(full)
                context.Succeed(requirement);
            
        
    
    注册授权处理程序(注意没有“LimiterOrFullRequirementHandler”,这两个处理程序将处理组合策略要求):
    //Order of handlers is important - it determines their execution order in request pipeline
    services.AddScoped<IAuthorizationHandler, LimitedRequirementHandler>();
    services.AddScoped<IAuthorizationHandler, FullRequirementHandler>();
    现在我们需要检索所有AuthorizeAny 属性并使用ImpromptuInterface(或任何其他用于动态创建类型实例的工具)动态地为它们创建需求:
    using ImpromptuInterface;

    List<AuthorizeAnyAttribute> attributes  = new List<AuthorizeAnyAttribute>();

    foreach(Type type in Assembly.GetExecutingAssembly().GetTypes().Where(type => type.IsAssignableTo(typeof(ControllerBase)))) 
        attributes .AddRange(Attribute.GetCustomAttributes(type , typeof(AuthorizeAnyAttribute))
            .Cast<AuthorizeAnyAttribute>()
            .Where(x => x.Policy != null));
        foreach(var methodInfo in type.GetMethods()) 
            attributes .AddRange(Attribute.GetCustomAttributes(methodInfo , typeof(AuthorizeAnyAttribute))
            .Cast<AuthorizeAnyAttribute>()
            .Where(x => x.Policy != null));
        
    
    
    //Add base requirement interface from which all requirements will be created on demand
    Dictionary<string, Type> baseRequirementTypes = new();
    baseRequirementTypes.Add("Limited", typeof(ILimitedRequirement));
    baseRequirementTypes.Add("Full", typeof(IFullRequirement));
    
    Dictionary<string, IAuthorizationRequirement> requirements = new();
    
    foreach(var attribute in attributes) 
        if(!requirements.ContainsKey(attribute.Policy)) 
            Type[] requirementTypes = new Type[attribute.Policies.Length];
            for(int i = 0; i < attribute.Policies.Length; i++) 
                if(!baseRequirementTypes.TryGetValue(attribute.Policies[i], out Type requirementType))
                    throw new ArgumentException($"Requirement for attribute.Policies[i] policy doesn't exist");
                requirementTypes[i] = requirementType;
            
            //Creating instance of combined requirement dynamically
            IAuthorizationRequirement newRequirement = new  .ActLike(requirementTypes);
            requirements.Add(attribute.Policy, newRequirement);
        
    
    注册所有创建的需求
    services.AddAuthorization(options => 
        foreach(KeyValuePair<string, IAuthorizationRequirement> item in requirements) 
             options.AddPolicy(item.Key, x => x.AddRequirements(item.Value));
        
    

如果默认 AuthorizeAttribute 与自定义 AuthorizeAnyAttribute 处理相同,则上述解决方案允许处理与 OR-combined 相同的单个要求

如果上述解决方案太过分了,则始终可以使用手动组合类型创建和注册:

    创建组合的“有限或全部”政策要求:
    public class LimitedOrFullRequirement : ILimitedRequirement, IFullRequirement  
    如果这两个要求也必须单独使用(除了使用组合的“有限或完整”策略),请为单个要求创建接口实现:
    public class LimitedRequirement : ILimitedRequirement  
    public class FullRequirement : IFullRequirement  
    注册策略(请注意,注释掉的策略完全可选注册):
    services.AddAuthorization(options => 
                options.AddPolicy("Limited Or Full",
                    policy => policy.AddRequirements(new LimitedOrFullRequirement()));
                //If these policies also have single use, they need to be registered as well
                //options.AddPolicy("Limited",
                //  policy => policy.AddRequirements(new LimitedRequirement()));
                //options.AddPolicy("Full",
                //  policy => policy.AddRequirements(new FullRequirement()));
            );

【讨论】:

以上是关于ASP.NET 5 授权两个或多个策略(或组合策略)的主要内容,如果未能解决你的问题,请参考以下文章

ASP.NET 如何授权网站

在 Asp.Net Core 的 Razor 页面中检查登录用户授权策略

ASP.NET Core 使用 JWT 自定义角色/策略授权需要实现的接口

ASP.NET 核心策略检查中缺少“aud”声明会阻止授权,但它位于 id 令牌中

如何使用 AuthorizationHandlerContext 在 ASP.NET Core 2 自定义基于策略的授权中访问当前的 HttpContext

asp.net core 2.0 web api基于JWT自定义策略授权