首页 > 编程 > PHP > 正文

thinkphp和onethink之权限管理

2020-03-22 19:49:18
字体:
来源:转载
供稿:网友
  • onethink权限管理主要分为两个方面一种菜单节点检测,另一种是动态检测(未实现)。
    第一次进入系统后,在Admin/Controller/AdminController.html' target='_blank'>class.php中权限验证的代码为:

              define('IS_ROOT',   is_administrator());                if(!IS_ROOT && C('ADMIN_ALLOW_IP')){                    // 检查IP地址访问                    if(!in_array(get_client_ip(),explode(',',C('ADMIN_ALLOW_IP')))){                        $this->error('403:禁止访问');                    }                }                 $access =   $this->accessControl();                if ( $access === false ) {                    $this->error('403:禁止访问');                }elseif( $access === null ){                    $dynamic        =   $this->checkDynamic();//动态检测的代码,返回null                    if( $dynamic === null ){                        //检测非动态权限                        $rule  = strtolower(MODULE_NAME.'/'.CONTROLLER_NAME.'/'.ACTION_NAME);                        if(!IS_ROOT) {                            if (!$this->checkRule($rule, array('in', '1,2'))) {                                $this->error('未授权访问!');                                exit;                            }                        }                    }elseif( $dynamic === false ){                        $this->error('未授权访问!');                    }                }

    在onethink的数据库中有四张表是和权限管理有关联的,这里写图片描述
    其中rule表对应的是此系统中所有的url生成的规则表,group表对应的是某个分组所拥有的权限,也就是某个分组可以访问的url集合。group_access代表的某个用户属于某个组,extend表主要用来实现动态检测。

    在/Admin/Controller/AdminController.class.php中进行的第一次权限检测,

         /**     * action访问控制,在 **登陆成功** 后执行的第一项权限检测任务     *     * @return boolean|null  返回值必须使用 `===` 进行判断     *     *   返回 **false**, 不允许任何人访问(超管除外)     *   返回 **true**, 允许任何管理员访问,无需执行节点权限检测     *   返回 **null**, 需要继续执行节点权限检测决定是否允许访问     *      */  final protected function accessControl(){        $allow = C('ALLOW_VISIT');        $deny  = C('DENY_VISIT');#这两项配置存储在config表中        $check = strtolower(CONTROLLER_NAME.'/'.ACTION_NAME);        if ( !empty($deny)  && in_array_case($check,$deny) ) {            return false;//非超管禁止访问deny中的方法        }        if ( !empty($allow) && in_array_case($check,$allow) ) {            return true;        }        return null;//需要检测节点权限    }

    权限认证的配置在/ThinkPHP/Library/Think/Auth.class.php中如图:
    这里写图片描述
    规则验证中最重要的函数为check()函数:

        public function check($name, $uid, $type=1, $mode='url', $relation='or') {        if (!$this->_config['AUTH_ON'])#如果没有开启验证,返回true            return true;        $authList = $this->getAuthList($uid,$type); //获取用户拥有的权限列表        if (is_string($name)) {            $name = strtolower($name);            if (strpos($name, ',') !== false) { #如果是多个,将其拆分成数组                $name = explode(',', $name);            } else {                $name = array($name);            }        }        $list = array(); //保存验证通过的规则名        if ($mode=='url') {            $REQUEST = unserialize( strtolower(serialize($_REQUEST)) );        }        foreach ( $authList as $auth ) {            $query = preg_replace('/^.+?/U','',$auth);#获得参数字符串            if ($mode=='url' && $query!=$auth ) {                parse_str($query,$param); //解析规则中的param 生成一个数组,键值对对应url中的键值对                $intersect = array_intersect_assoc($REQUEST,$param);#输出$REQUEST 和$param的交集                $auth = preg_replace('/?.*$/U','',$auth);#此时的$auth为url路径                if ( in_array($auth,$name) && $intersect==$param ) {  //如果节点相符且url参数满足                    $list[] = $auth ;                }                }else if (in_array($auth , $name)){#遍历用户拥有的权限数组,如果某个权限存在于$name数组中,则将其放入$list数组,假设用户拥有权限为1,2,3,4,5,                                                    #需要验证的权限为2,6.那么会将2放入$list数组,                $list[] = $auth ;            }        }        exit;        if ($relation == 'or' and !empty($list)) {#如上个例子中,当为或时,只要$list数组不为空,既只要满足一个权限就可以            return true;        }        $diff = array_diff($name, $list);        if ($relation == 'and' and empty($diff)) {#如上例中,当为与时,需要满足$List数组和$name数组完全相同才可以,既$name中的权限全部存在于$auth中            return true;        }        return false;    }

    因为后台的控制器都继承了AdminController控制器,所以每打开一个url,都会首先检测改用户是否具有权限。
    进入后台后,进入到用户的权限管理页面,如默认用户组,执行的方法为:

        public function access(){        $this->updateRules();//首先执行此方法,此方法根据menu表中的数据更新rule表中的数据,具体见下方代码        $auth_group = M('AuthGroup')->where( array('status'=>array('egt','0'),'module'=>'admin','type'=>AuthGroupModel::TYPE_ADMIN) )                                    ->getfield('id,id,title,rules');        $node_list   = $this->returnNodes();//查询menu表,获得主菜单数组以及子菜单数组        $map         = array('module'=>'admin','type'=>AuthRuleModel::RULE_MAIN,'status'=>1);        $main_rules  = M('AuthRule')->where($map)->getField('name,id');//查询rule表获得主菜单的url和id值        $map         = array('module'=>'admin','type'=>AuthRuleModel::RULE_URL,'status'=>1);        $child_rules = M('AuthRule')->where($map)->getField('name,id');//查询rule表获得子菜单的url和id值        $this->assign('main_rules', $main_rules);        $this->assign('auth_rules', $child_rules);        $this->assign('node_list',  $node_list);        $this->assign('auth_group', $auth_group);        $this->assign('this_group', $auth_group[(int)$_GET['group_id']]);//当前用户组        $this->meta_title = '访问授权';        $this->display('managergroup');    }
      public function updateRules(){        //需要新增的节点必然位于$nodes        $nodes    = $this->returnNodes(false); #returnNodes查询出表menu中的所有菜单项,生成一个二维数组,其中的一个值如下:    /*    0 => array:4 [▼     *    'title' => '文档列表'     *  'url' => 'Admin/article/index'     *'tip' => ''     *'pid' => '2'     *    ]    */        $AuthRule = M('AuthRule');        $map      = array('module'=>'admin','type'=>array('in','1,2'));        //需要更新和删除的节点必然位于$rules        $rules    = $AuthRule->where($map)->order('name')->select();//查询出属于admin模块的所有规则,其中type=1代表url,type=2代表主菜单        //构建insert数据        $data     = array();//保存需要插入和更新的新节点        foreach ($nodes as $value){            $temp['name']   = $value['url'];            $temp['title']  = $value['title'];            $temp['module'] = 'admin';            if($value['pid'] >0){                $temp['type'] = AuthRuleModel::RULE_URL;//RULE_URL为1代表url            }else{                $temp['type'] = AuthRuleModel::RULE_MAIN;//RULE_MAIN为2代表主菜单            }            $temp['status']   = 1;            $data[strtolower($temp['name'].$temp['module'].$temp['type'])] = $temp;//去除重复项        }        /*$data的一个子数组如下:此时$data存储的为menu表中的数据         *   'admin/article/indexadmin1' => array:5 [▼         *   'name' => 'Admin/article/index'         *   'title' => '文档列表'         *   'module' => 'admin'         *   'type' => 1         *   'status' => 1          ]         */        $update = array();//保存需要更新的节点        $ids    = array();//保存需要删除的节点的id        foreach ($rules as $index=>$rule){//$data是菜单生成的数组,此循环的作用是根据菜单数组,来进行规则表的增删改操作,如果规则数组中的某个键和菜单数组的键相同则将菜单数组                                        //中的该值放入$updata表,将规则数组的值放入$diff表,如果规则数组中某个值不存在与菜单数组中,说明规则数组中的该值需要删除            $key = strtolower($rule['name'].$rule['module'].$rule['type']);            if ( isset($data[$key]) ) {//如果数据库中的规则与配置的节点匹配,说明是需要更新的节点                $data[$key]['id'] = $rule['id'];//为需要更新的节点补充id值                $update[] = $data[$key];                unset($data[$key]);                unset($rules[$index]);                unset($rule['condition']);                $diff[$rule['id']]=$rule;            }elseif($rule['status']==1){                $ids[] = $rule['id'];            }        }        if ( count($update) ) { //$update是菜单表生成的,$diff是规则表生成的            foreach ($update as $k=>$row){                if ( $row!=$diff[$row['id']] ) {//判断菜单数组的数据是否有更新,如果有更新,规则表也进行更新                    $AuthRule->where(array('id'=>$row['id']))->save($row);                }            }        }        if ( count($ids) ) { //            $AuthRule->where( array( 'id'=>array('IN',implode(',',$ids)) ) )->save(array('status'=>-1));            //删除规则是否需要从每个用户组的访问授权表中移除该规则?        }        //需要更新的$data已经unset掉,剩余的数据为为新增数据,执行add操作        if( count($data) ){            $AuthRule->addAll(array_values($data));//array_values函数将关联数组变为索引数组,只作用的一维        }        if ( $AuthRule->getDbError() ) {            trace('['.__METHOD__.']:'.$AuthRule->getDbError());            return false;        }else{            return true;        }    }

    生成菜单数据后,view层使用三层循环将数据输出,循环的数据如内容
    这里写图片描述

                    <volist name='node_list' id='node' >//第一次循环主菜单                                        <dl class='checkmod'>                                            <dt class='hd'>                                                <label class='checkbox'><input class='auth_rules rules_all' type='checkbox' name='rules[]' value='<?php echo $main_rules[$node['url']] ?>'>{$node.title}管理</label>                                            </dt>                                            <dd class='bd'>                                                <present name='node['child']'>                                                <volist name='node['child']' id='child' > //第二次循环子菜单                                                    <div class='rule_check'>                                                        <div>                                                            <label class='checkbox' <notempty name='child['tip']'>title='{$child.tip}'</notempty>>                                                           <input class='auth_rules rules_row' type='checkbox' name='rules[]' value='<?php echo $auth_rules[$child['url']] ?>'/>{$child.title}                                                            </label>                                                        </div>                                                            <notempty name='child['operator']'>                                                                                                                                  <volist name='child['operator']' id='op'> //第三次循环操作                                                                       <label class='checkbox' <notempty name='op['tip']'>title='{$op.tip}'</notempty>>                                                                           <input class='auth_rules' type='checkbox' name='rules[]'                                                                           value='<?php echo $auth_rules[$op['url']] ?>'/>{$op.title}                                                                       </label>                                                                   </volist>                                                                                                                           </notempty>                                                        </div>                                                </volist>                                                </present>                                            </dd>                                        </dl>                                    </volist>

    对于如何生成菜单数据主要调用了两个函数为:returnNodes()和函数list_to_tree(),
    returnNodes()函数的代码为:

     final protected function returnNodes($tree = true){        static $tree_nodes = array();        if ( $tree && !empty($tree_nodes[(int)$tree]) ) {            return $tree_nodes[$tree];        }        if((int)$tree){            $list = M('Menu')->field('id,pid,title,url,tip,hide')->order('sort asc')->select();            foreach ($list as $key => $value) {  //给$list数组的url字段加上模块名                if( stripos($value['url'],MODULE_NAME)!==0 ){                    $list[$key]['url'] = MODULE_NAME.'/'.$value['url'];                }            }            $nodes = list_to_tree($list,$pk='id',$pid='pid',$child='operator',$root=0);//将菜单生成树形结构            foreach ($nodes as $key => $value) {                if(!empty($value['operator'])){                    $nodes[$key]['child'] = $value['operator'];//将键名由operator更改为child                    unset($nodes[$key]['operator']);                }            }        }else{//返回一维数组            $nodes = M('Menu')->field('title,url,tip,pid')->order('sort asc')->select();            foreach ($nodes as $key => $value) {                if( stripos($value['url'],MODULE_NAME)!==0 ){                    $nodes[$key]['url'] = MODULE_NAME.'/'.$value['url'];                }            }        }        $tree_nodes[(int)$tree]   = $nodes;        return $nodes;    }

    list_to_tree()函数的代码为:

    function list_to_tree($list, $pk='id', $pid = 'pid', $child = '_child', $root = 0) {    // 创建Tree    $tree = array();    if(is_array($list)) {        // 创建基于主键的数组引用        $refer = array();        foreach ($list as $key => $data) {            $refer[$data[$pk]] = & $list[$key];//将$list数组以引用的方式转换成$refer数组,键为子数组的id值        }        foreach ($list as $key => $data) {            // 判断是否存在parent            $parentId =  $data[$pid];            if ($root == $parentId) {//此时pid = 0为主菜单,直接放入$tree数组                $tree[] =& $list[$key];            }else{                if (isset($refer[$parentId])) {//此时当前url的父菜单在$refer中                    $parent =& $refer[$parentId];                    $parent[$child][] =& $list[$key];//                    dump($parent);                }            }        }    }    return $tree;}

    这里写图片描述
    函数list_to_tree()仅使用的几行代码就生成了一个树,现分析如下 :
    $parent =& $refer[$parentId]是以引用的方式赋值,所以改变$parent的值,就相当于改变$refer的值,又因为 $refer[$data[$pk]] = $list[$key], 所以改变$refer的值就相当于改变$list的值,又因为$tree[] =& $list[$key]所以改变$list的值就相当于改变$tree的值,总结为:改变了$parent的值就相当于改变了$tree的值,以上图为例,它是生成的树形结构中的用户分类,当遍历到用户信息时,在$refer中含有用户这个数组,所以会在用户这个数组中添加一个子元素,键为operator,值为用户信息这个数组,当遍历到新增用户时,同样查找$refer,在$refer这个数组中含有用户信息这个数组,所以给用户信息这个数组添加一个子元素,键为operator,值为新增用户这个数组,因为使用引用的关系,所以$tree数组的每一个元素都是到此函数执行到最后一步才确定的,比如当用户信息添加了子元素新增用户时,用户这个数组也会跟着进行变动。

    PHP编程

    郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。

  • 发表评论 共有条评论
    用户名: 密码:
    验证码: 匿名发表