程序员人生 网站导航

thinkphp和onethink之权限管理

栏目:php教程时间:2016-06-04 08:15:26

onethink权限管理主要分为两个方面1种菜单节点检测,另外一种是动态检测(未实现)。
第1次进入系统后,在Admin/Controller/AdminController.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的数据库中有4张表是和权限管理有关联的,这里写图片描述
其中rule表对应的是此系统中所有的url生成的规则表,group表对应的是某个分组所具有的权限,也就是某个分组可以访问的url集合。group_access代表的某个用户属于某个组,extend表主要用来实现动态检测。

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

/** * action访问控制,在 **登陆成功** 后履行的第1项权限检测任务 * * @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 生成1个数组,键值对对应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数组不为空,既只要满足1个权限就能够 return true; } $diff = array_diff($name, $list); if ($relation == 'and' and empty($diff)) {#如上例中,当为与时,需要满足$List数组和$name数组完全相同才可以,既$name中的权限全部存在于$auth中 return true; } return false; }

由于后台的控制器都继承了AdminController控制器,所以每打开1个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中的所有菜单项,生成1个2维数组,其中的1个值以下: /* 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的1个子数组以下:此时$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函数将关联数组变成索引数组,只作用的1维 } if ( $AuthRule->getDbError() ) { trace('['.__METHOD__.']:'.$AuthRule->getDbError()); return false; }else{ return true; } }

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

<volist name="node_list" id="node" >//第1次循环主菜单 <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" > //第2次循环子菜单 <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']"> <span class="child_row"> <volist name="child['operator']" id="op"> //第3次循环操作 <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> </span> </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{//返回1维数组 $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()仅使用的几行代码就生成了1个树,现分析以下 :
$parent =& $refer[$parentId]是以援用的方式赋值,所以改变$parent的值,就相当于改变$refer的值,又由于 $refer[$data[$pk]] = $list[$key], 所以改变$refer的值就相当于改变$list的值,又由于$tree[] =& $list[$key]所以改变$list的值就相当于改变$tree的值,总结为:改变了$parent的值就相当于改变了$tree的值,以上图为例,它是生成的树形结构中的用户分类,当遍历到用户信息时,在$refer中含有用户这个数组,所以会在用户这个数组中添加1个子元素,键为operator,值为用户信息这个数组,当遍历到新增用户时,一样查找$refer,在$refer这个数组中含有用户信息这个数组,所以给用户信息这个数组添加1个子元素,键为operator,值为新增用户这个数组,由于使用援用的关系,所以$tree数组的每个元素都是到此函数履行到最后1步才肯定的,比如当用户信息添加了子元素新增用户时,用户这个数组也会随着进行变动。

------分隔线----------------------------
------分隔线----------------------------

最新技术推荐