ACL – 权限控制

权限控制

权限控制顾名思义就是控制什么“人”能(不能)访问什么(“操作”)。在身份认证中,我们已经知道了人是谁,现在这里就介绍yangzie如何控制这些人能做什么,yangzie是基于ACL来实现授权功能的,那么这里需要先阐明几个概念:

Access Control Object

ACO访问控制对象,也就是ARO要请求访问的对象,在yangzie中也就是M/C/A(见yangzie基本原则),每个具体的ACO也有一个标识,就按照M/C/A的格式进行标识,比如order模块下面的增加订单add,假如他都组织在index控制器下(index_controller),action为add;那么该ACO的标识就是:/order/index/add;

根据具体的业务要求,ACO可以定义到某个具体的Action级别,或者Controller级别或者整个module级别

Access Request Object

ARO访问请求对象,也就是要请求ACO的对象,通常他指“人”,各种各样的类型的人,每种“类型”有一个唯一的标识,用于区别一类人,比如某种角色的,也可以唯一标识一个人;

这个标识yangzie的设计是从大类/小类/具体某个用户id然后以/进行分割,举个例子,假如系统中存在这这几种角色,超级管理员,普通管理员,销售;可能这几种角色的ARO标识看起来是这个样子:

  • 超级管理员:/admin/root
  • 普通管理员:/admin/normal
  • 销售:/salesman
  • 管理员A:/admin/normal/12344
  • 为什么要这样设计,是因为在进行授权时可以把权限分配给组或者具体某个类型或者具体某个人;然后具体的某个用户,如上面的管理员A,在验证权限时,再从具体id到大类来逐一验证,直到找到具体的权限是允许还是拒绝为止。

    ACL

    ARO和ACO分别定义了“操作”和“人”,那么剩下的就是把这两个关联起来,从而达到什么人能访问什么的目的;这个关联的地方就是AccessControlList;在yangzie中它是一个配置文件,位于app/__aros_acos__.php;默认的内容如下:

    
    function yze_get_aco_desc($aconame) {
        foreach ( ( array ) yze_get_acos_aros () as $aco => $desc ) {
            if (preg_match ( "{^" . $aco . "}", $aconame )) {
                return @$desc ['desc'];
            }
        }
        return '';
    }
    function yze_get_ignore_acos() {
        return array();
    }
    function yze_get_acos_aros() {
        $array = array (
                "/" => array (//module/controller/action
                        "deny" => "",
                        "allow" => array (
                                "*" //aro
                        ),
                        "desc" => ""//功能说明 
                )
    		);
        
    	return $array;
    }
    

    该文件主要包含3个函数:

  • yze_get_acos_aros:该方法返回ACL列表
  • yze_get_ignore_acos:该方法返回忽略权限控制的ACO,当ARO请求这些内容时忽略权限控制
  • yze_get_aco_desc: 助手方法,返回ACO功能的描述,
  • ACL的格式

    ACL是一个数组,格式如下:

    array (
                "ACO1 name" => array (
                        "deny" => "",//拒绝的ARO
                        "allow" => array (
                                "*" //允许的ARO
                        ),
                        "desc" => ""//ACO功能说明 
                ),
                "ACO2 name" => array (
                        "deny" => "",//拒绝的ARO
                        "allow" => "*", //允许的ARO,
                        "desc" => ""//ACO功能说明 
                ),
    );

    ACO name是ACO的标识,根据权限控制的级别可以具体定义到Action级别,或者只定义到Module级别;

    deny定义黑名单,拒绝里面列出的ARO的访问;allow定义白名单,允许里面列出的ARO

    ACL定义规则

    1. ACO和ARO都可以使用正则表达
    2. 黑名单优先级大于白名单
    3. 两者都可以采用”*”来代表所有
    4. 如果要指定具体的aro,deny和allow中以数组的形式列出ARO

    一个真实的例子:

    
    function yze_get_ignore_acos() {
        return array(
                "/card/front",
                "/package/package/grab",
                "/package/package/grabfail",
                "/package/front",
                "/zb/middlepage/view",
                "/wxbbs/front",
                "/zbad/front",
        );
    }
    function yze_get_acos_aros() {
        $array = array (
                "/admin" => array (
                        "deny" => "*",
                        "allow" => array (
                                "/admin" 
                        ),
                        "desc" => "后端管理" 
                ),
                "/sp/consumers" => array (
                        "deny" => "*",
                        "allow" => array (
                                "/sp"
                        ),
                        "desc" => "粉丝管理"
                )
        );
        if (YDWXP_TYPE == YDWXP_TYPE_MARKET || YDWXP_TYPE == YDWXP_TYPE_ALL) {
            $array ['/card'] = array (
                    "deny" => "*",
                    "allow" => array ("/sp"),
                    "desc" => "卡券管理" 
            );
            $array ['/card/card/*consume'] = array (
                    "deny" => "*",
                    "allow" => array ("/sp"),
                    "desc" => "卡券核销"
            );
            $array ['/card/merchants'] = array (
                    "deny" => "*",
                    "allow" => array ("/sp/super","/sp/common"),
                    "desc" => "商户管理" 
            );
        }
        
    	return $array;
    }
    

    ARO怎么定义

    ACO是根据M/C/A来定义的,这很明确和具体,但ARO标识怎么根据用户来定义?这也是通过hook来实现的,在app/hooks/auth.php中已经定义注册了该hook,博狗官网博狗bodog者只需要实现即可,这里有一个真实的例子:

    
    YZE_Hook::add_hook ( YZE_FILTER_GET_USER_ARO_NAME, function  ( $data ) {
    	if ( !@$_SESSION [ 'admin' ] )return "/";
    	if(is_a($_SESSION['admin'], "\\app\\sp\\Consumer_Model")){
    	   return "/consumer";
    	}
        if($_SESSION [ 'admin' ]->sp_id) {
           //是否子商户
           if ( ! isset($_SESSION [ 'is_sub_merchant' ]) ) {
               $_SESSION [ 'is_sub_merchant' ] = YZE_Hook::do_hook(YDMARKET_IS_SUB_MERCHANT, $_SESSION [ 'admin' ]->sp_id);
           }
           if ($_SESSION [ 'is_sub_merchant' ]) {
               return "/".$_SESSION [ 'admin' ]->user->type."/sub/".$_SESSION [ 'admin' ]->type;
           }
        }
    	return "/".$_SESSION [ 'admin' ]->user->type."/".$_SESSION [ 'admin' ]->type;
    } );
    

    根据自己的业务系统的用户类型,自行设计自己的ARO标识接口。

    能灵活的配置权限吗?

    ACL列表属于事先硬编码的控制列表,但在实际的业务环节中很多权限都是需要动态分配的,比如给某中角色设置权限,给具体某个人设置权限;这在yangzie中如何做呢?

    yangzie有两个预留函数get_permissions($aroname),get_user_permissions()

    这两个函数由博狗官网博狗bodog者实现,分别返回指定aroname的acl或者返回某个具体用户的acl,这通常是具体的业务要求决定的,返回的仍然是acl数组结构。这两个函数放在任意的自动包含文件中(见__config__.php说明),但要注意

    这两个函数必须属于顶级命名空间

    当有这两个函数的实现时,他们的返回结果将覆盖ACL里面的控制

    ACL的效果是什么

    当配置好ACL后,如果一个用户访问被deny的内容,如某个action,某个controller或者某个module;yangzie将会抛出YZE_Permission_Deny_Exception异常,博狗官网博狗bodog者可以通过hook YZE_FILTER_YZE_EXCEPTION来监听然后做响应的处理,默认情况下将显示500页面;

    这就是说yangzie会根据ACL来验证用户的权限,如果用户没有权限就会立即抛出异常,这在进入博狗官网博狗bodog者的业务逻辑之前就已经处理,博狗官网博狗bodog者无需在自己的功能代码中去验证当前用户是谁,是否有权限方法;博狗官网博狗bodog者就只关注自己的业务逻辑即可。

    GET和POST的特殊情况

    yangzie的Action包含两种情况,get方式和post方法,比如一个增加用户的例子,假如M/C/A是:users/index/add;那么他对应的post action就是users/index/post_add,会多一个post前缀,如果在ACL需要针对这两种情况单独控制,比如允许get但不允许post,也就是允许看,但不允许提交,那在ACL中需要分配针对两个action(add,post_add)进行处理

    如何通过ACL来控制输出

    在实际的业务系统中,通常需要根据权限来控制输出的响应中某些部分用户可见,某些部分用户不可见,那么这可以通过两种方式来实现。

    方法一:通过YZE_ACL::get_instance()->check_byname($aroname, $aconame)来判断并输出

    
    if(YZE_ACL::get_instance()->check_byname($aroname, $aconame)){
    
    // your output html
    
    }
    

    方法二:通过YZE_ACL::get_instance()->begin_check_permission($id, $aroname, $aconame)….YZE_ACL::get_instance()->end_check_permission($id, $aroname, $aconame)来输出

    
    YZE_ACL::get_instance()->begin_check_permission($id, $aroname, $aconame)
    
    // your output html
    
    YZE_ACL::get_instance()->end_check_permission($id, $aroname, $aconame)
    

    根据你的习惯来选择,如果你不喜欢跨度很大的if(){},那用第二种方式是个好选择