ThinkPHP5权限控制
我在用ThinkPHP5做开发的时候发现,它没有权限类,自己写太麻烦,于是就想到了把TP3里面的权限类拿来修改使用,结果这种方法是可行的,下面记录附上修改后的Auth.php权限类
1 <?php 2 // +---------------------------------------------------------------------- 3 // | ThinkPHP [ WE CAN DO IT JUST THINK IT ] 4 // +---------------------------------------------------------------------- 5 // | Copyright (c) 2011 http://thinkphp.cn All rights reserved. 6 // +---------------------------------------------------------------------- 7 // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) 8 // +---------------------------------------------------------------------- 9 // | Author: luofei614 <weibo.com/luofei614> 10 // +---------------------------------------------------------------------- 11 // 功能:权限类 12 namespace app\admin\controller; 13 /** 14 * 权限认证类 15 * 功能特性: 16 * 1,是对规则进行认证,不是对节点进行认证。用户可以把节点当作规则名称实现对节点进行认证。 17 * $auth=new Auth(); $auth->check('规则名称','用户id') 18 * 2,可以同时对多条规则进行认证,并设置多条规则的关系(or或者and) 19 * $auth=new Auth(); $auth->check('规则1,规则2','用户id','and') 20 * 第三个参数为and时表示,用户需要同时具有规则1和规则2的权限。 当第三个参数为or时,表示用户值需要具备其中一个条件即可。默认为or 21 * 3,一个用户可以属于多个用户组(think_auth_group_access表 定义了用户所属用户组)。我们需要设置每个用户组拥有哪些规则(think_auth_group 定义了用户组权限) 22 * 23 * 4,支持规则表达式。 24 * 在think_auth_rule 表中定义一条规则时,如果type为1, condition字段就可以定义规则表达式。 如定义{score}>5 and {score}<100 表示用户的分数在5-100之间时这条规则才会通过。 25 */ 26 27 //数据库 28 /* 29 -- ---------------------------- 30 -- think_auth_rule,规则表, 31 -- id:主键,name:规则唯一标识, title:规则中文名称 status 状态:为1正常,为0禁用,condition:规则表达式,为空表示存在就验证,不为空表示按照条件验证 32 -- ---------------------------- 33 DROP TABLE IF EXISTS `think_auth_rule`; 34 CREATE TABLE `think_auth_rule` ( 35 `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT, 36 `name` char(80) NOT NULL DEFAULT '', 37 `title` char(20) NOT NULL DEFAULT '', 38 `type` tinyint(1) NOT NULL DEFAULT '1', 39 `status` tinyint(1) NOT NULL DEFAULT '1', 40 `condition` char(100) NOT NULL DEFAULT '', # 规则附加条件,满足附加条件的规则,才认为是有效的规则 41 PRIMARY KEY (`id`), 42 UNIQUE KEY `name` (`name`) 43 ) ENGINE=MyISAM DEFAULT CHARSET=utf8; 44 -- ---------------------------- 45 -- think_auth_group 用户组表, 46 -- id:主键, title:用户组中文名称, rules:用户组拥有的规则id, 多个规则","隔开,status 状态:为1正常,为0禁用 47 -- ---------------------------- 48 DROP TABLE IF EXISTS `think_auth_group`; 49 CREATE TABLE `think_auth_group` ( 50 `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT, 51 `title` char(100) NOT NULL DEFAULT '', 52 `status` tinyint(1) NOT NULL DEFAULT '1', 53 `rules` char(80) NOT NULL DEFAULT '', 54 PRIMARY KEY (`id`) 55 ) ENGINE=MyISAM DEFAULT CHARSET=utf8; 56 -- ---------------------------- 57 -- think_auth_group_access 用户组明细表 58 -- uid:用户id,group_id:用户组id 59 -- ---------------------------- 60 DROP TABLE IF EXISTS `think_auth_group_access`; 61 CREATE TABLE `think_auth_group_access` ( 62 `uid` mediumint(8) unsigned NOT NULL, 63 `group_id` mediumint(8) unsigned NOT NULL, 64 UNIQUE KEY `uid_group_id` (`uid`,`group_id`), 65 KEY `uid` (`uid`), 66 KEY `group_id` (`group_id`) 67 ) ENGINE=MyISAM DEFAULT CHARSET=utf8; 68 */ 69 70 class Auth{ 71 72 //默认配置 73 protected $_config = array( 74 'auth_on' => true, // 认证开关 75 'auth_type' => 1, // 认证方式,1为实时认证;2为登录认证。 76 'auth_group' => '__AUTH_GROUP__', // 用户组数据表名 77 'auth_group_access' => 'auth_group_access', // 用户-用户组关系表 78 'auth_rule' => 'auth_rule', // 权限规则表 79 'auth_user' => 'member' // 用户信息表 80 ); 81 82 public function __construct() { 83 $t=config('auth_config'); 84 if (config('auth_config')) { 85 //可设置配置项 auth_config, 此配置项为数组。 86 $this->_config = array_merge($this->_config, config('auth_config')); 87 } 88 } 89 90 /** 91 * 检查权限 92 * @param name string|array 需要验证的规则列表,支持逗号分隔的权限规则或索引数组 93 * @param uid int 认证用户的id 94 * @param string mode 执行check的模式 95 * @param relation string 如果为 'or' 表示满足任一条规则即通过验证;如果为 'and'则表示需满足所有规则才能通过验证 96 * @return boolean 通过验证返回true;失败返回false 97 */ 98 public function check($name, $uid, $type=1, $mode='url', $relation='or') { 99 if (!$this->_config['auth_on']) 100 return true; 101 $authList = $this->getAuthList($uid,$type); //获取用户需要验证的所有有效规则列表 102 if (is_string($name)) { 103 $name = strtolower($name); 104 if (strpos($name, ',') !== false) { 105 $name = explode(',', $name); 106 } else { 107 $name = array($name); 108 } 109 } 110 $list = array(); //保存验证通过的规则名 111 if ($mode=='url') { 112 $REQUEST = unserialize( strtolower(serialize($_REQUEST)) ); 113 } 114 foreach ( $authList as $auth ) { 115 $query = preg_replace('/^.+\?/U','',$auth); 116 if ($mode=='url' && $query!=$auth ) { 117 parse_str($query,$param); //解析规则中的param 118 $intersect = array_intersect_assoc($REQUEST,$param); 119 $auth = preg_replace('/\?.*$/U','',$auth); 120 if ( in_array($auth,$name) && $intersect==$param ) { //如果节点相符且url参数满足 121 $list[] = $auth ; 122 } 123 }else if (in_array($auth , $name)){ 124 $list[] = $auth ; 125 } 126 } 127 if ($relation == 'or' and !empty($list)) { 128 return true; 129 } 130 $diff = array_diff($name, $list); 131 if ($relation == 'and' and empty($diff)) { 132 return true; 133 } 134 return false; 135 } 136 137 /** 138 * 根据用户id获取用户组,返回值为数组 139 * @param uid int 用户id 140 * @return array 用户所属的用户组 array( 141 * array('uid'=>'用户id','group_id'=>'用户组id','title'=>'用户组名称','rules'=>'用户组拥有的规则id,多个,号隔开'), 142 * ...) 143 */ 144 public function getGroups($uid) { 145 static $groups = array(); 146 if (isset($groups[$uid])) 147 return $groups[$uid]; 148 $user_groups = \think\Db::name($this->_config['auth_group_access']) 149 ->alias('a') 150 ->join($this->_config['auth_group']." g", "g.id=a.group_id") 151 ->where("a.uid='$uid' and g.status='1'") 152 ->field('uid,group_id,title,rules')->select(); 153 $groups[$uid] = $user_groups ? $user_groups : array(); 154 return $groups[$uid]; 155 } 156 157 /** 158 * 获得权限列表 159 * @param integer $uid 用户id 160 * @param integer $type 161 */ 162 protected function getAuthList($uid,$type) { 163 static $_authList = array(); //保存用户验证通过的权限列表 164 $t = implode(',',(array)$type); 165 if (isset($_authList[$uid.$t])) { 166 return $_authList[$uid.$t]; 167 } 168 if( $this->_config['auth_type']==2 && isset($_SESSION['_auth_list_'.$uid.$t])){ 169 return $_SESSION['_auth_list_'.$uid.$t]; 170 } 171 172 //读取用户所属用户组 173 $groups = $this->getGroups($uid); 174 $ids = array();//保存用户所属用户组设置的所有权限规则id 175 foreach ($groups as $g) { 176 $ids = array_merge($ids, explode(',', trim($g['rules'], ','))); 177 } 178 $ids = array_unique($ids); 179 if (empty($ids)) { 180 $_authList[$uid.$t] = array(); 181 return array(); 182 } 183 184 $map=array( 185 'id'=>array('in',$ids), 186 'type'=>$type, 187 'status'=>1, 188 ); 189 //读取用户组所有权限规则 190 $rules = \think\Db::name($this->_config['auth_rule'])->where($map)->field('condition,name')->select(); 191 192 //循环规则,判断结果。 193 $authList = array(); // 194 foreach ($rules as $rule) { 195 if (!empty($rule['condition'])) { //根据condition进行验证 196 $user = $this->getUserInfo($uid);//获取用户信息,一维数组 197 198 $command = preg_replace('/\{(\w*?)\}/', '$user[\'\\1\']', $rule['condition']); 199 //dump($command);//debug 200 @(eval('$condition=(' . $command . ');')); 201 if ($condition) { 202 $authList[] = strtolower($rule['name']); 203 } 204 } else { 205 //只要存在就记录 206 $authList[] = strtolower($rule['name']); 207 } 208 } 209 $_authList[$uid.$t] = $authList; 210 if($this->_config['auth_type']==2){ 211 //规则列表结果保存到session 212 $_SESSION['_auth_list_'.$uid.$t]=$authList; 213 } 214 return array_unique($authList); 215 } 216 217 /** 218 * 获得用户资料,根据自己的情况读取数据库 219 */ 220 protected function getUserInfo($uid) { 221 static $userinfo=array(); 222 if(!isset($userinfo[$uid])){ 223 $userinfo[$uid]=\think\Db::name($this->_config['auth_user'])->where(array('uid'=>$uid))->find(); 224 } 225 return $userinfo[$uid]; 226 } 227 228 }
那么有了这个类,怎么用它呢?其实很简单,复制注释中的sql语句,建立三张表,然后写一个公共的类继承控制器类,然后在需要验证权限的类中继承这个公共类就行了,下面附上这个公共类Common.php
1 <?php 2 /** 3 * 作者:魏安来 4 * 功能:后台公共类,控制后台权限 5 * 日期:2017/11/13 6 */ 7 namespace app\admin\controller; 8 use think\Controller; 9 use think\Session; 10 use think\Request; 11 12 class Common extends Controller { 13 14 /** 15 * 初始化方法 16 * 功能:验证权限 17 */ 18 public function _initialize() { 19 /*判断用户是否有Session,没有就退出登录*/ 20 if(!Session::get('uid')) { 21 $this->redirect('login/index'); 22 } 23 24 $request = Request::instance(); 25 $auth = new Auth(); 26 27 /*************验证权限*************/ 28 $controller = $this->uncamelize(request()->controller()); 29 $name = $controller.'/'.request()->action(); 30 $res = $auth->check($name,Session::get('uid')); 31 //定义一个数组,放置可以不限制权限的页面 32 $noCheck = ['index/index','index/logout']; 33 //超级管理员不用验证权限 34 $superadmin = $auth->getGroups(Session::get('uid')); 35 if($superadmin[0]['group_id'] != 1) { 36 if(!in_array($name,$noCheck)) { 37 if(!$res) { 38 $this->error('你没有权限'); 39 } 40 } 41 } 42 } 43 44 45 /** 46 * 驼峰命名转下划线命名 47 * 思路: 48 * 小写和大写紧挨一起的地方,加上分隔符,然后全部转小写 49 */ 50 public function uncamelize($camelCaps,$separator='_') 51 { 52 return strtolower(preg_replace('/([a-z])([A-Z])/', "$1" . $separator . "$2", $camelCaps)); 53 } 54 55 } 56 57 ?>
这样就搞定了权限验证了.不过原则上没有权限不应该显示的,上面的验证方法可以阻止企图绕过权限验证的行为,比如直接在地址栏输入链接访问,所以上面应该的权限验证应该算是隐形的防线,关于没有权限的用户不显示,可以参考下面的写法,我就直接附上代码了:
1 /** 2 * 功能:显示菜单 3 */ 4 public function menu() { 5 /*获取所有菜单*/ 6 $allmenu = \think\Db::name('menu')->where(1)->select(); 7 /*查询二级菜单*/ 8 $res = db('menu')->where('pid','<>',0)->where('isshow','=',1)->order('sort asc')->select(); 9 $auth = new Auth(); 10 $superadmin = $auth->getGroups(Session::get('uid')); 11 if($superadmin[0]['group_id'] != 1) {//超级管理员不需要验证权限 12 foreach($allmenu as $key=>$value) { 13 $rules[$key] = $value['controller'].'/'.$value['method']; 14 } 15 //获取权限规则 16 $authList = $auth->getAuthList(Session::get('uid'),1); 17 //数组交集 18 $intersection = array_intersect($rules,$authList); 19 $arrtmp = array(); 20 //将一维数组拼接成二维数组 21 foreach($intersection as $key=>$val){ 22 $tempArr = explode('/',$val); 23 $arrtmp[$key]['controller'] = $tempArr[0]; 24 } 25 $intersection = $arrtmp; 26 $menuArr = array(); 27 //筛选符合权限的数组,重新组成左侧导航菜单数据 28 foreach($allmenu as $key=>$value){ 29 foreach($intersection as $val){ 30 if(!empty(array_intersect($val,$value))){ 31 $menuArr[$key] = $value; 32 } 33 } 34 } 35 $allmenu = $menuArr; 36 //筛选符合权限的数组,重新组成左侧导航菜单数据 37 foreach($res as $key=>$value){ 38 foreach($intersection as $val){ 39 if(!empty(array_intersect($val,$value))){ 40 $resArr[$key] = $value; 41 } 42 } 43 } 44 $res = $resArr; 45 } 46 $num = count($allmenu); 47 $this->view->assign('child_menu',$res); 48 $this->view->assign('num',$num); 49 $this->view->assign('allmenu',$allmenu); 50 }