欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-03-20 08:54:14
1.5 基于策略的授权 在上篇中,已经讲到了授权访问(authorization)的四种方式。其中Razor Pages授权约定和简单授权二种方式更像是身份认证(authentication) ,因为只要是合法用户登录就能访问资源。 而角色授权和声明授权二种方式是真正的授权访问(authorizat ......

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) 处理程序注入到服务集合,采用单例

    services.addsingleton<iauthorizationhandler, minimumagehandler>();

    在userclaim用户声明表中,保存一条符合该策略授权的数据,当启动程序,访问alcoholpurchase资源时,进入授权处理程序minimumagehandler中, 执行context.succeed(requirement)后, 授权成功。

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

  

  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"

 

 参考文献