Thinkphp基于规则的Auth权限认证类

PS:onethink是基于该权限认证类实现,Auth类作为官方类库,在Library\Think里面。
其实Auth类也是基于角色访问控制RBAC扩展的,具体到节点的权限校验方式还是需要根据业务需求扩展。

Auth和RBAC

Rbac:

  1. Rbac是基于节点控制,根据3级节点,module,controller,action,节点类似与树形结构,3级节点间相互有关联
  2. 表关系:用户表->用户角色关联表->角色表->角色节点关联表->节点表
  3. 根据3级节点控制,粒度到操作action,每个节点为单一的模块,控制器或操作

Auth:

  1. 是对规则进行认证,不是对节点进行认证。用户可以把节点当作规则名称实现对节点进行认证。
  2. 表关系:用户表->用户和用户组关联表->用户组表->规则表
  3. 根据规则控制,可自由定制不同的规则,非常自由,同一个规则内可以定制多个不同节点(中间的关系:OR AND)
  4. 可定制规则表达式,比如定制积分表达式
  5. 一个用户可以属于多个用户组。我们需要设置每个用户组拥有哪些规则

RBAC是按节点进行认证的,如果要控制比节点更细的权限就有点困难了,比如页面上面的操作按钮, 我想判断用户权限来显示这个按钮, 如果没有权限就不会显示这个按钮; 再比如我想按积分进行权限认证, 积分在0-100时能干什么, 在101-200时能干什么。 这些权限认证用RABC都很困难。
而Auth权限认证, 它几乎是全能的, 除了能进行节点认证, 上面说的RABC很难认证的两种情况,它都能实现。

 

Auth的认证过程

数据库表介绍

用户表自行设计哟,提供uid即可

 1 //数据库
 2 /*
 3 -- ----------------------------
 4 -- think_auth_rule,规则表,
 5 -- id:主键,name:规则唯一标识, title:规则中文名称 status 状态:为1启用,为0禁用,condition:规则表达式,为空表示存在就验证,不为空表示按照条件验证
 6 -- ----------------------------
 7 DROP TABLE IF EXISTS `think_auth_rule`;
 8 CREATE TABLE `think_auth_rule` ( 
 9     `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT, 
10     `name` char(80) NOT NULL DEFAULT '', 
11     `title` char(20) NOT NULL DEFAULT '',  
12     `type` tinyint(1) NOT NULL DEFAULT '1',   #1-url,2-主菜单,这个字段可以根据业务逻辑,自己定义类别,见getAuthList函数
13     `status` tinyint(1) NOT NULL DEFAULT '1', 
14     `condition` char(100) NOT NULL DEFAULT '',  # 规则附件条件,满足附加条件的规则,才认为是有效的规则
15     PRIMARY KEY (`id`), 
16     UNIQUE KEY `name` (`name`)
17 ) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
18 -- ----------------------------
19 -- think_auth_group 用户组表,
20 -- id:主键, title:用户组中文名称, rules:用户组拥有的规则id, 多个规则","隔开,status 状态:为1启用,为0禁用
21 -- ----------------------------
22 DROP TABLE IF EXISTS `think_auth_group`;
23 CREATE TABLE `think_auth_group` (
24     `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
25     `title` char(100) NOT NULL DEFAULT '',
26     `status` tinyint(1) NOT NULL DEFAULT '1',#用户组状态:为1正常,为0禁用,-1为删除
27     `rules` char(80) NOT NULL DEFAULT '', #rule表的ID值,用逗号分隔,这样这个用户组就可以用多种规则了。
28     PRIMARY KEY (`id`)
29 ) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
30 -- ----------------------------
31 -- think_auth_group_access 用户组明细表
32 -- uid:用户id,group_id:用户组id
33 -- ----------------------------
34 DROP TABLE IF EXISTS `think_auth_group_access`;
35 CREATE TABLE `think_auth_group_access` ( 
36     `uid` mediumint(8) unsigned NOT NULL, 
37     `group_id` mediumint(8) unsigned NOT NULL,
38     UNIQUE KEY `uid_group_id` (`uid`,`group_id`), 
39     KEY `uid` (`uid`),
40     KEY `group_id` (`group_id`)
41 ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
42 */

Thinkphp中Auth源码

仅仅160行~

  1 class Auth{
  2  
  3     //默认配置
  4     protected $_config = array(
  5         'AUTH_ON'           => true,                      // 认证开关
  6         'AUTH_TYPE'         => 1,                         // 认证方式,1为实时认证;2为登录认证。
  7         'AUTH_GROUP'        => 'auth_group',        // 用户组数据表名
  8         'AUTH_GROUP_ACCESS' => 'auth_group_access', // 用户-用户组关系表
  9         'AUTH_RULE'         => 'auth_rule',         // 权限规则表
 10         'AUTH_USER'         => 'member'             // 用户信息表
 11     );
 12  
 13     public function __construct() {
 14         $prefix = C('DB_PREFIX');
 15         $this->_config['AUTH_GROUP'] = $prefix.$this->_config['AUTH_GROUP'];
 16         $this->_config['AUTH_RULE'] = $prefix.$this->_config['AUTH_RULE'];
 17         $this->_config['AUTH_USER'] = $prefix.$this->_config['AUTH_USER'];
 18         $this->_config['AUTH_GROUP_ACCESS'] = $prefix.$this->_config['AUTH_GROUP_ACCESS'];
 19         if (C('AUTH_CONFIG')) {
 20             //可设置配置项 AUTH_CONFIG, 此配置项为数组。
 21             $this->_config = array_merge($this->_config, C('AUTH_CONFIG'));
 22         }
 23     }
 24  
 25     /**
 26       * 检查权限
 27       * @param name string|array  需要验证的规则列表,支持逗号分隔的权限规则或索引数组
 28       * @param uid  int           认证用户的id
 29       * @param string mode        执行check的模式
 30       * @param relation string    如果为 'or' 表示满足任一条规则即通过验证;如果为 'and'则表示需满足所有规则才能通过验证
 31       * @return boolean           通过验证返回true;失败返回false
 32      */
 33     public function check($name, $uid, $type=1, $mode='url', $relation='or') {
 34         if (!$this->_config['AUTH_ON'])
 35             return true;
 36         //获取用户需要验证的所有有效规则列表
 37         $authList = $this->getAuthList($uid,$type);
 38         //是否验证多个规则,包含逗号,变成数组
 39         if (is_string($name)) {
 40             $name = strtolower($name);
 41             if (strpos($name, ',') !== false) {
 42                 $name = explode(',', $name);
 43             } else {
 44                 $name = array($name);
 45             }
 46         }
 47         $list = array(); //保存验证通过的规则名
 48         if ($mode=='url') {
 49             //序列化,小写,又反序列化?
 50             $REQUEST = unserialize( strtolower(serialize($_REQUEST)) );
 51         }
 52         foreach ( $authList as $auth ) {
 53             //获取url的query参数
 54             $query = preg_replace('/^.+\?/U','',$auth);
 55             if ($mode=='url' && $query!=$auth ) {
 56                 //解析规则中的param,使其变成PHP变量
 57                 parse_str($query,$param); 
 58                 //获取$REQUEST与$param带索引检查计算数组的交集
 59                 $intersect = array_intersect_assoc($REQUEST,$param);
 60                 //获取auth中的的query参数
 61                 $auth = preg_replace('/\?.*$/U','',$auth);
 62                 if ( in_array($auth,$name) && $intersect==$param ) {  //如果节点相符且url参数满足
 63                     $list[] = $auth ;
 64                 }
 65             }else if (in_array($auth , $name)){
 66                 //例如,$name='Admin/index',不带query参数,就不是url模式,那么就直接判断是否包含,否则如上代码判断url参数是否一致。
 67                 $list[] = $auth ;
 68             }
 69         }
 70         if ($relation == 'or' and !empty($list)) {
 71             return true;
 72         }
 73         $diff = array_diff($name, $list);
 74         if ($relation == 'and' and empty($diff)) {
 75             return true;
 76         }
 77         return false;
 78     }
 79  
 80     /**
 81      * 根据用户id获取用户组,返回值为数组
 82      * @param  uid int     用户id
 83      * @return array       用户所属的用户组 array(
 84      *     array('uid'=>'用户id','group_id'=>'用户组id','title'=>'用户组名称','rules'=>'用户组拥有的规则id,多个,号隔开'),
 85      *     ...)  
 86      */
 87     public function getGroups($uid) {
 88         static $groups = array();
 89         if (isset($groups[$uid]))
 90             return $groups[$uid];
 91         $user_groups = M()
 92             ->table($this->_config['AUTH_GROUP_ACCESS'] . ' a')
 93             ->where("a.uid='$uid' and g.status='1'")
 94             ->join($this->_config['AUTH_GROUP']." g on a.group_id=g.id")
 95             ->field('uid,group_id,title,rules')->select();
 96         $groups[$uid]=$user_groups?:array();
 97         return $groups[$uid];
 98     }
 99  
100     /**
101      * 获得权限列表
102      * @param integer $uid  用户id
103      * @param integer $type
104      */
105     protected function getAuthList($uid,$type) {
106         static $_authList = array(); //保存用户验证通过的权限列表
107         $t = implode(',',(array)$type);
108         if (isset($_authList[$uid.$t])) {
109             return $_authList[$uid.$t];
110         }
111         if( $this->_config['AUTH_TYPE']==2 && isset($_SESSION['_AUTH_LIST_'.$uid.$t])){
112             return $_SESSION['_AUTH_LIST_'.$uid.$t];
113         }
114  
115         //读取用户所属用户组,一个用户可以属于多个用户组
116         $groups = $this->getGroups($uid);
117         $ids = array();//保存用户所属用户组设置的所有权限规则id
118         foreach ($groups as $g) {
119             $ids = array_merge($ids, explode(',', trim($g['rules'], ',')));
120         }
121         $ids = array_unique($ids);
122         if (empty($ids)) {
123             $_authList[$uid.$t] = array();
124             return array();
125         }
126  
127         $map=array(
128             'id'=>array('in',$ids),
129             'type'=>$type,
130             'status'=>1,
131         );
132         //读取用户组所有权限规则
133         $rules = M()->table($this->_config['AUTH_RULE'])->where($map)->field('condition,name')->select();
134  
135         //循环规则,判断结果。
136         $authList = array();   //
137         foreach ($rules as $rule) {
138             if (!empty($rule['condition'])) { //根据condition进行验证
139                 $user = $this->getUserInfo($uid);//获取用户信息,一维数组
140                 //使用用户的信息进行条件判断
141                 //正则表达式替换,如定义{score}>5  and {score}<100  表示用户的分数在5-100之间时这条规则才会通过。
142                 $command = preg_replace('/\{(\w*?)\}/', '$user[\'\\1\']', $rule['condition']);
143                 //eval() 函数把字符串按照 PHP 代码来计算。
144                 @(eval('$condition=(' . $command . ');'));
145                 if ($condition) {
146                     $authList[] = strtolower($rule['name']);
147                 }
148             } else {
149                 //只要存在就记录
150                 $authList[] = strtolower($rule['name']);
151             }
152         }
153         $_authList[$uid.$t] = $authList;
154         if($this->_config['AUTH_TYPE']==2){
155             //如果是登录认证,规则列表结果保存到session,不是实时认证,就可以直接用session缓存了
156             $_SESSION['_AUTH_LIST_'.$uid.$t]=$authList;
157         }
158         return array_unique($authList);
159     }
160  
161     /**
162      * 获得用户资料,根据自己的情况读取数据库
163      */
164     protected function getUserInfo($uid) {
165         static $userinfo=array();
166         if(!isset($userinfo[$uid])){
167              $userinfo[$uid]=M()->where(array('uid'=>$uid))->table($this->_config['AUTH_USER'])->find();
168         }
169         return $userinfo[$uid];
170     }
171  
172 }

实际使用

非常简单,就一个函数搞定
public function check($name, $uid, $type=1, $mode='url', $relation='or')

    /**
     * 权限检测
     * @param string  $rule    检测的规则,例如Admin/Category/add,Admin/Category/edit,Admin/User/changeStatus?method=deleteUser
     * @param string  $mode    check模式
     * @return boolean
     */
    final protected function checkRule($rule, $type=AuthRuleModel::RULE_URL, $mode='url'){
        if(IS_ROOT){
            return true;//管理员允许访问任何页面
        }
        static $Auth    =   null;
        if (!$Auth) {
            $Auth       =   new \Think\Auth();
        }
        //$rule  = strtolower(MODULE_NAME.'/'.CONTROLLER_NAME.'/'.ACTION_NAME);
        if(!$Auth->check($rule,UID,$type,$mode)){
            return false;
        }
        return true;
    }

参考

http://www.thinkphp.cn/topic/4029.html
http://www.thinkphp.cn/extend/675.html

 

 

posted @ 2016-04-05 14:49  leestar54  阅读(2532)  评论(0编辑  收藏  举报