关于speed框架的权限管理

#1 xiuluozhou

这两天在用WDZ+SP框架做个后台管理系统,涉及到权限控制的问题,研究了下框架自带的权限管理,呵呵,觉得有点麻烦,特别是数据初始化的时候,需要手工去增加N多数据。然后我就秀逗了下,也从框架层面考虑,设计了一个权限管理的方法,仅供大牛们参考
====================割掉======================
思路是这样的,因为根据用户操作的特点(一般一个操作为一个URL),加上SP的特点(一个URL对应唯一的一个C和一个A),那么就是针对URL的访问控制。SP自带的ACL也是这么做的,下面,做法不同的地方在于,我是通过扩展spController,载入所有Controller目录中的所有的类,进行反射,获取相关的权限设置(后面阐述怎么设置),然后联合当前用户在数据库中存储的相关权限配置进行全局控制。
举个例子(为什么没有代码编辑器?):
/app/controller/user.php
/**
* user
*
* @author ShuraChow
* @docheck true
* @AclSubjectName  管理员管理
*/
class user extends baseController
{
    function __construct(){
        parent::__construct();   
    }
   
    /**
     * user-index
     *
     * @author ShuraChow
     * @docheck true
     * @AclSubjectName  管理员列表
     */
    function index(){
        
    }
   
    /**
     * user-useradd
     *
     * @author ShuraChow
     * @docheck true
     * @AclSubjectName  添加管理员
     */
    function useradd(){
      
    }
   
    /**
     * user-useredit
     *
     * @author ShuraChow
     * @docheck true
     * @AclSubjectName  编辑管理员
     */
    function useredit(){
      
    }
   
    /**
     * user-doadduser
     *
     * @author ShuraChow
     * @docheck true
     * @AclSubjectName  删除管理员
     */
    function deleteuser(){
               
    }
   
    /**
     * user-_doadduser
     *
     * @author ShuraChow
     * @docheck true
     * @fatherAction c_user_a_useradd
     * @AclSubjectName  添加用户操作
     */
    function _doadduser(){
   
    }
    /**
     * user-_doadduser
     *
     * @author ShuraChow
     * @docheck true
     * @fatherAction c_user_a_useredit
     * @AclSubjectName  编辑用户操作
     */
    function _doedit(){
      
    }
}

2012-11-30 11:00:59

#2 xiuluozhou

关键是类及方法的注释。
@docheck 为是否进行鉴权,
@fatherAction 为继承某个RUL的权限(目前只能有二级)
@AclSubjectName 在用户权限管理中显示的权限名称

下面是baseController.php
class baseController extends spController{

    function __construct(){
        parent::__construct();
        $this->spc = parent;
        $this->spModel = spClass('spModel');
        $this->basectl = $this;
        $this->syskey = $GLOBALS['SYS_DEFINE']['SYSKEY'];
        switch($this->syskey){
            case '_MC_':$this->initMC();break;
            case '_ADMIN_':$this->initAdmin();break;
            default://$this->initANPO();break;
        }
        //print_r($this->giveAllAcl());
        //print_r($this->Aclarr);
        //print_r($this->UserAcl);exit;
    }
   
    function initAdmin(){
        $this->getRouter($this->syskey);
        $this->Acl = $this->spModel->getCaches('_SYS'.$this->syskey.'ACL_TREE');        //用户管理权限tree使用
        $this->Aclarr = $this->spModel->getCaches('_SYS'.$this->syskey.'ACL_ARR');    //所有需要鉴权的栏目
        $this->Router = $this->spModel->getCaches('_SYS'.$this->syskey.'ACL');        //全局用户权限鉴权使用
        $this->UserAcl = empty($_SESSION[$this->syskey.'userinfo'])?array(md5('c_index_a_index'.$this->syskey)):unserialize($_SESSION[$this->syskey.'userinfo']['user_rule']);
        //print_r($this->Router);exit;
        $this->UserInfo = $_SESSION[$this->syskey.'userinfo'];//print_r($_SESSION['bms_userinfo']);
        $this->checkLogin($this->syskey);
        $this->checkAcl();
        $this->initUI();
        $this->HTML = spClass('MakeHTML');
        $this->initTemplateFunc();
    }
   
    function initMC(){
        $this->getRouter($this->syskey);
        $this->Acl = $this->spModel->getCaches('_SYS'.$this->syskey.'ACL_TREE');        //用户管理权限tree使用
        $this->Aclarr = $this->spModel->getCaches('_SYS'.$this->syskey.'ACL_ARR');    //所有需要鉴权的栏目
        $this->Router = $this->spModel->getCaches('_SYS'.$this->syskey.'ACL');        //全局用户权限鉴权使用
        $this->UserAcl = empty($_SESSION[$this->syskey.'userinfo'])?array(md5('c_index_a_index'.$this->syskey)):unserialize($_SESSION[$this->syskey.'userinfo']['user_rule']);
        $this->UserInfo = $_SESSION[$this->syskey.'userinfo'];//print_r($_SESSION['mcenter_userinfo']);
        $this->checkLogin($this->syskey);
        $this->checkAcl();
        $this->initUI();
        $this->HTML = spClass('MakeHTML');
        $this->initTemplateFunc();
    }
   
   
    function initUI(){
        $a = $this->spArgs('a','index');
        $c = $this->spArgs('c','index');
        $this->ListPageTabName = 'c_'.$c.'_a_'.$a;
    }
   
    function initTemplateFunc(){
        spAddViewBlock("getads", array('Sblock', 'do_translation'));      
        //spAddViewFunction('getads', array( 'Sblock', 'get_ad'));
    }

    function setUserInfo($userinfo){
        $this->UserInfo = $userinfo;
        $_SESSION[$this->syskey.'userinfo'] = $userinfo;
        $this->UserAcl = unserialize($userinfo['user_rule']);
    }
   
    function getCaches($name){
        if($this->cacheTime==0)return false;
        return spAccess('r',  $name);
    }
   
    function setCaches($name,$value,$always=false){
        if($always===true)spAccess('w', $name, $value, -1);
        else spAccess('w', $name, $value, $this->cacheTime);
    }
   
      
    function checkLogin(){
        $a = $this->spArgs('a','index');
        $c = $this->spArgs('c','index');
        //print_r($_SESSION);exit;
        if(empty($this->UserInfo['user_id'])){
            if('c_'.$c.'_a_'.$a!='c_index_a_login'){
                $this->goahead("请先登录",spUrl("index","login"));
                exit;
            }
        }
    }
   
    //查询父级权限
    function getFaherKey($key){
        $ACL = $this->spModel->getCaches('_SYS'.$this->syskey.'ACL_TREE');
        foreach($ACL as $keys=>$value){
            foreach($value['items'] as $k=>$v){
                if($key==$k){
                    if(!empty($v['fatherAcl'])){
                        return $v['fatherAcl'];
                    }
                }
            }
        }
        return false;
    }
   
    //鉴权
    function checkAcl(){
        $a = $this->spArgs('a','index');
        $c = $this->spArgs('c','index');
        $fkey = 'c_'.$c.'_a_'.$a;
        $key = $this->getFaherKey($fkey);//echo $key;exit;
        $key = $key==''?md5($fkey.$this->syskey):md5($key.$this->syskey);
        if(!in_array('c_'.$c.'_a_'.$a,$this->Aclarr) or in_array($key,$this->UserAcl)){
            return true;
        }else{
            //$this->display("sys/no_acl.php");
            echo '';exit;
            exit;
        }
    }

   
    /**
     * index
     * 获取所有路由
     * 规则如下:
     * 1、controller中,必须添加注释,否则权限功能会缺失(包括控制器类的注释和对应方法注释,构造函数不需注释)!,规则如示例中所示。
     * 2、@author:作者姓名。
     * 3、@docheck:是否鉴权
     * 4、@fatherAction:鉴权时当此参数不为空时,则继承此参数值对应的栏目权限鉴权(只支持两级,即不可继承已继承权限的栏目!)。例:c_index_a_index
     * 5、@AclSubjectName:该路由所指向的应用中文名称
        示例:
            /**
             * user
             *
             * @author ShuraChow
             * @docheck true
             * @fatherAction
             * @AclSubjectName  用户管理
             */
             class user extends baseController{
            
                /**
                 * user-index
                 *
                 * @author ShuraChow
                 * @docheck true
                 * @fatherAction c_user_a_useredit
                 * @AclSubjectName  用户列表
                 */
                function index(){
                    ……
                }
             }                                                         
    /**
     * @author ShuraChow
     */
    function getRouter($sys='_MC_'){
        $cname = array();
        //if(empty($_SESSION['_SYSACL'])){
        if(!$this->spModel->getCaches('_SYS'.$sys.'ACL')){
            //递归载入所有控制类
            $cp = getAllFile($GLOBALS['SYS_DEFINE']['APP_PATH'].'/'.'controller',true);
            if(is_array($cp['php'])){
                foreach ($cp['php'] as $p){
                    require_once $p;
                }
            }
            $allclass = get_declared_classes();
            foreach($cp['php'] as $k=>$v){
                $_classn = basename($v,".php");
                //echo $_classn.'
';
                $_class = new ReflectionClass($_classn);
                $Doc = $_class->getDocComment();
                preg_match_all('/@docheck\s+(\S*)/i',$Doc,$Acldo);
                preg_match_all('/@fatherAction\s+(\S*)/i',$Doc,$fatherAcl);
                if($Acldo[1][0]=='true'){
                    preg_match_all('/@AclSubjectName\s+(\S*)/i',$Doc,$AclSubjectName);
                    $AclTree['c_'.$_classn]['subject'] = $AclSubjectName[1][0];
                    $AclTree['c_'.$_classn]['fatherAcl'] = $fatherAcl[1][0];
                    $AclTree['c_'.$_classn]['key'] =  md5('c_'.$_classn.$sys);
                    $Acl[md5('c_'.$_classn.$sys)] = $AclSubjectName[1][0];
                    $AclArr[] = 'c_'.$_classn;
                    //print_r($_class->getMethods());
                    foreach($_class->getMethods() as $rk => $rv){
                        //if($rv->class == $_classn and !preg_match('/_/',$rv->name)){
                        if($rv->class == $_classn){
                            $_method = new ReflectionMethod($_classn, $rv->name);
                            $Doc = $_method->getDocComment();
                            preg_match_all('/@docheck\s+(\S*)/i',$Doc,$Acldo);
                            preg_match_all('/@fatherAction\s+(\S*)/i',$Doc,$fatherAcls);
                            if($Acldo[1][0]=='true'){
                                preg_match_all('/@AclSubjectName\s+(\S*)/i',$Doc,$AclSubjectName);
                                $AclTree['c_'.$_classn]['items']['c_'.$_classn.'_a_'.$rv->name]['subject'] = $AclSubjectName[1][0];
                                $AclTree['c_'.$_classn]['items']['c_'.$_classn.'_a_'.$rv->name]['fatherAcl'] = $fatherAcls[1][0];
                                $AclTree['c_'.$_classn]['items']['c_'.$_classn.'_a_'.$rv->name]['key'] = md5('c_'.$_classn.'_a_'.$rv->name.$sys);
                                $Acl[md5('c_'.$_classn.'_a_'.$rv->name.$sys)] = $AclSubjectName[1][0];
                                $AclArr[] = 'c_'.$_classn.'_a_'.$rv->name;
                            }
                        }
                    }
                }
            }
            //始终数据缓存,仅当删除缓存文件时才更新
            $this->spModel->always = true;
            $this->spModel->setCaches('_SYS'.$sys.'ACL_TREE',$AclTree);
            $this->spModel->setCaches('_SYS'.$sys.'ACL_ARR',$AclArr);
            $this->spModel->setCaches('_SYS'.$sys.'ACL',$Acl);
            //$_SESSION['_SYS'.$sys.'ACL'] = $AclTree;
        }
        //print_r($_SESSION['_SYS'.$sys.'ACL']);
        //print_r($this->spModel->getCaches('_SYS'.$sys.'ACL_TREE'));
        //return $this->spModel->getCaches('_SYS'.$sys.'ACL_TREE');
    }
   
    function giveAllAcl(){
        return array_keys($this->Router);
    }
   
}

2012-11-30 11:01:37

#3 xiuluozhou

对于用户的权限,保存在数据库中为serialize(array(md5('c_user_a_index'),……))的形式,鉴权时,进行MD5码比对。

先写这么多,老板来了
欢迎jake及各位大牛拍砖和建议


2012-11-30 11:01:52

#4 jake

SP的ACL权限,有分两类。在实际应用中,数据表真正要记录controller并不多,其实没必要用反射那种低性能的东西来自动获取,而且有不灵活的问题。

两类权限控制要对应不同类型的应用:
有限控制 —— 前台大后台小
强制控制 —— 前台小后台大
所以两类要记录的控制器名称,都只是上述说的“小”的那一部分。

当然,你可以反过来辛苦点,用有限控制去做后台大的,这样就会有“手工去增加N多数据”的劳苦活。

2012-11-30 14:27:31

#5 xiuluozhou

jake 发表于 2012-11-30 14:27
SP的ACL权限,有分两类。在实际应用中,数据表真正要记录controller并不多,其实没必要用反射那种低性能的 ...
反射出的数据,一般只要程序没有改动就不会变的,所以做了永久的数据缓存,不过当程序变更时,也可以进行数据更新,目前唯一考虑到不灵活的地方是,一旦原有的程序进行了变更,替换掉了原来的同名控制器,变成了新的功能,那么历史数据中的用户权限变更会比较难搞。
我也想听听jake发现的不灵活的地方。:loveliness:

其实没用spACL的原因还有个,因为客户需要权限细化到每个人,spACL是以角色来划分权限的,这样的话有点不太好弄(可能没怎么太深入研究)。。

2012-11-30 14:50:33

#6 jake

不灵活一方面是你说的修改和变更的问题。

最重要的是,你这段只能作为初始化入库的时候的一个入库操作(反射),实际上每个控制器对应的权限,还是需要人工去分配(包括权限对应的控制器/名称、中文名)。所以还是我上面说的,实际开发中,真正要入库的名称是比较少的,要不要写个过于复杂的程序(有上百行了)来入库少数几个控制器/动作(一般也就共10-20个action)的名称呢?

acl其实可以针对角色也可以针对单个用户,把用户当角色就行。

当然,鉴于你没有用过acl,那也不好说。sp的acl最早是在几个实际应用的后台型项目里面归总出来的,后来加上有限控制这种前台型的功能,所以许多问题是实际上已经被考虑到的。

2012-11-30 16:43:07

#7 xiuluozhou

{:soso_e179:}学习了,回去再研究研究

2012-11-30 17:19:27

#8 niumeng

:@:loveliness:;P:L:curse:

2015-04-10 11:14:39

#9 和珅

我都是简单粗暴,直接在构造函数中限制

2015-11-12 11:15:14