美文网首页工作生活
asp.net core系列 50 Identity 授权(中)

asp.net core系列 50 Identity 授权(中)

作者: 懒懒的程序员一枚 | 来源:发表于2019-07-03 16:08 被阅读0次
    1.5 基于策略的授权

    在上篇中,已经讲到了授权访问(authorization)的四种方式。其中Razor Pages授权约定和简单授权二种方式更像是身份认证(authentication) ,因为只要是合法用户登录就能访问资源。 而角色授权和声明授权二种方式是真正的授权访问(authorization)。
    下面继续讲authorization的第五种方式--策略授权。策略授权由一个或多个需求(也可以称"要求")组成(需求:TRequirement)。它在程序启动时注册为授权服务配置的一部分。在ConfigureServices方法中注册。
    (1) 注册策略授权
    创建了一个名为"AtLeast21"的策略授权,这个策略的需求是最小年龄需求,策略通过参数对象(IAuthorizationRequirement)提供,它要求最低年龄是21岁。

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    
                    services.AddAuthorization(options =>
                     {
                         options.AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber"));
                        // MinimumAgeRequirement参数对象 实现了IAuthorizationRequirement
                         options.AddPolicy("AtLeast21", policy => policy.Requirements.Add(new MinimumAgeRequirement(21)));
                     });
    }
    

    (2) 策略授权应用到mvc的控制器或Razor Pages

    //用户购买酒业务, 策略授权应用到控制器,要求用户年龄不能低于21岁
    [Authorize(Policy = "AtLeast21")]
    public class AlcoholPurchaseController : Controller
    {
        public IActionResult Index() => View();
    }
    
    //策略授权到razor pages的PageModel类
    [Authorize(Policy = "AtLeast21")]
    public class AlcoholPurchaseModel : PageModel
    {
    }
    

    在razor pages中,策略还可以应用到razor page授权约定中。

    public static PageConventionCollection AuthorizeFolder(this PageConventionCollection conventions, string folderPath, string policy);
    

    (3) Requirement策略授权需求
    策略授权需求实现IAuthorizationRequirement接口,用于策略需求对象参数传递。MinimumAgeRequirement就是一个需求参数对象。

    using Microsoft.AspNetCore.Authorization;
    public class MinimumAgeRequirement : IAuthorizationRequirement
    {
        public int MinimumAge { get; }
    
        public MinimumAgeRequirement(int minimumAge)
        {
            MinimumAge = minimumAge;
        }
    }
    

    (4) 策略授权处理程序类
    授权处理程序负责评估要求的属性(指策略授权逻辑处理,把当前用户的年龄与策略要求年龄进行验证)。 授权处理程序会针对提供的AuthorizationHandlerContext 来评估要求,确定是否允许访问或拒绝。

    实现策略授权处理程序,需要继承AuthorizationHandler<TRequirement>,其中TRequirement就是参数对象。另外,一个处理程序也可以通过实现 IAuthorizationHandler 来处理多个类型的要求。

    下面是一对一关系的示例(一个Handler处理一个TRequirement对象),评估最低年龄要求:

    public class MinimumAgeHandler: AuthorizationHandler<MinimumAgeRequirement>
        {
            protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                            MinimumAgeRequirement requirement)
            {
                if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth &&
                                                c.Issuer == "LOCAL AUTHORITY"))
                {
                    //TODO: Use the following if targeting a version of
                    //.NET Framework older than 4.6:
                    //      return Task.FromResult(0);
                    return Task.CompletedTask;
                }
    
                var dateOfBirth = Convert.ToDateTime(
                    context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth &&
                                                c.Issuer == "LOCAL AUTHORITY").Value);
    
                int calculatedAge = DateTime.Today.Year - dateOfBirth.Year;
                if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
                {
                    calculatedAge--;
                }
    
                if (calculatedAge >= requirement.MinimumAge)
                {
                   //满足的要求作为其唯一参数
                    context.Succeed(requirement);
                }
    
                //TODO: Use the following if targeting a version of
                //.NET Framework older than 4.6:
                //      return Task.FromResult(0);
                return Task.CompletedTask;
            }
        }
    

    上面代码是当前用户主休是否有一个由已知的受信任颁发者(Issuer)颁发的出生日期声明(ClaimTypes.DateOfBirth)。当前用户缺少声明时,无法进行授权,这种情况下会返回已完成的任务。如果存在声明时,会计算用户的年龄。 如果用户满足此要求所定义的最低年龄,则可以认为授权成功。 授权成功后,会调用 context.Succeed,使用满足的要求作为其唯一参数。
    (5) 处理程序注入到服务集合,采用单例
    在UserClaim用户声明表中,保存一条符合该策略授权的数据,当启动程序,访问AlcoholPurchase资源时,进入授权处理程序MinimumAgeHandler中, 执行context.Succeed(requirement)后, 授权成功。



     1.5.1 多个需求使用一个处理程序
    下面是多个需求(TRequirement)使用一个处理程序,Handler实现IAuthorizationHandler接口,下面示例是一个对多关系的权限处理程序,可以在其中处理三种不同类型的需求:

    public class PermissionHandler : IAuthorizationHandler
    {
        public Task HandleAsync(AuthorizationHandlerContext context)
        {
             //获取策略中的多个需求,返回IEnumerable<IAuthorizationRequirement>类型
            var pendingRequirements = context.PendingRequirements.ToList();
             
            foreach (var requirement in pendingRequirements)
            {
                //读取授权
                if (requirement is ReadPermission)
                {
                    if (IsOwner(context.User, context.Resource) ||
                        IsSponsor(context.User, context.Resource))
                    {
                        context.Succeed(requirement);
                    }
                }
                //编辑和删除授权
                else if (requirement is EditPermission ||
                         requirement is DeletePermission)
                {
                    if (IsOwner(context.User, context.Resource))
                    {
                        context.Succeed(requirement);
                    }
                }
            }
    
            //TODO: Use the following if targeting a version of
            //.NET Framework older than 4.6:
            //      return Task.FromResult(0);
            return Task.CompletedTask;
        }
    

    具体详细代码,查看官方示例, Github
      1.5.2 处理程序应返回什么? (有三种返回)
    (1) 处理程序通过调用 context.Succeed(IAuthorizationRequirement requirement) 并传递已成功验证的要求来表示成功。

    (2) 处理程序通常不需要处理失败(显示加context.Fail()),因为同一要求的其他处理程序(1.5.3)可能会成功。

    (3) 若要保证授权失败,即使其它要求处理程序会成功,也会失败,请调用context.Fail();
     
    1.5.3 一个需求应用在多个处理程序
    这里授权处理正好与15.1相反,下面这个示例是门禁卡授权策略需求, 你公司的门禁卡丢在家中,去公司后要求前台给个临时门禁卡来开门。这种情况下,只有一个需求,但有多个处理程序,每个处理程序针对单个要求进行检查。

     //策略授权需求,这里没有参数需求, 参数是硬编码在处理程序中
         public class BuildingEntryRequirement : IAuthorizationRequirement
          {
          }
    
    //门禁卡处理程序
    public class BadgeEntryHandler : AuthorizationHandler<BuildingEntryRequirement>
    {
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                       BuildingEntryRequirement requirement)
        {
                //需求参数硬编码 BadgeId 
            if (context.User.HasClaim(c => c.Type == "BadgeId" &&
                                           c.Issuer == "http://microsoftsecurity"))
            {
                context.Succeed(requirement);
            }
    
            //TODO: Use the following if targeting a version of
            //.NET Framework older than 4.6:
            //      return Task.FromResult(0);
            return Task.CompletedTask;
        }
    }
    
    //临时门禁卡处理程序
    public class TemporaryStickerHandler : AuthorizationHandler<BuildingEntryRequirement>
    {
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, 
                                                       BuildingEntryRequirement requirement)
        {
            //需求参数硬编码 TemporaryBadgeId
            if (context.User.HasClaim(c => c.Type == "TemporaryBadgeId" &&
                                           c.Issuer == "https://microsoftsecurity"))
            {
                // We'd also check the expiration date on the sticker.
                context.Succeed(requirement);
            }
    
            //TODO: Use the following if targeting a version of
            //.NET Framework older than 4.6:
            //      return Task.FromResult(0);
            return Task.CompletedTask;
        }
    }
    
      // 注册策略 
           services.AddAuthorization(options =>
                {
                      options.AddPolicy("BadgeEntry", policy =>policy.Requirements.Add(new BuildingEntryRequirement()));           
               });
    
     // 注入服务
         services.AddSingleton<IAuthorizationHandler, BadgeEntryHandler>();
         services.AddSingleton<IAuthorizationHandler, TemporaryStickerHandler>();
    

    当[Authorize(Policy = " BadgeEntry ")]应用到控制器后,只要有一个处理程序成功,则策略授权成功。需要在UserClaim用户声明表中维护好ClaimType。
     1.5.4 使用 func 满足策略
     有些情况下,策略很容易用代码实现。 可以在通过 Func<AuthorizationHandlerContext, bool> 策略生成器配置策略时提供需要声明 (RequireAssertion),例如上一个 BadgeEntryHandler 可以重写,如下所示:

    services.AddAuthorization(options =>
        {
           options.AddPolicy("BadgeEntry", policy =>
               policy.RequireAssertion(context =>
                  context.User.HasClaim(c =>
                      (c.Type == "BadgeId" ||
                       c.Type == "TemporaryBadgeId") &&
                       c.Issuer == "https://microsoftsecurity")));
        });
    

    总结:通过这二篇,熟悉了授权的五种方式,包括:

    Razor Pages授权约定
        简单授权
        角色授权
        声明授权
        策略授权

    其中声明授权包含了角色授权,通过ClaimTypes.Role 可以在声明中使用角色授权。

     public const string Role = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"
    

    参考文献

    基于策略的授权

    相关文章

      网友评论

        本文标题:asp.net core系列 50 Identity 授权(中)

        本文链接:https://www.haomeiwen.com/subject/hgqchctx.html