thinkphp3.23的auth详细源码解读(带实例)

thinkphp的auth权限认证对于做网站来讲是非常常用的一个功能,所以特别写一篇文章来帮助自己更好的记忆,毕竟好记性不如烂笔头!

先来看看auth所需要的三个表:

think_auth_group     //用户组表

表内的数据:

id为用户组id

title为用户组名称

status为用户组状态,0为禁用,1为启用

rules为用户组启用的权限规则id

 

think_auth_group_access     //用户组明细表

表内的数据:

uid对应think_user表里的用户id

group_id对应think_auth_group里的用户组id

 

think_auth_rule  //认证规则表

表内的数据:

id:规则id

name:规则唯一标识

title:规则中文名称

status:状态,0为禁用,1为启用

type:是否用condition进行验证,1为默认验证,0为不用condition验证

condition:验证条件,空为存在就验证,不为空则按照此内容进行验证

 

think_user   //用户表

表内的数据:

id为用户id

username为用户名

pass为密码

score为积分

这里由于只是示例,所以设置非常简单,密码也是演示一下,具体设置可以根据自己来,而且如果验证的条件不想放入用户表中,可以另外新建一个表,确定有用户id即可,然后配置的时候使用这个表。

 

上面是auth的数据表结构,虽然代码很清楚,但可能不太直观,为了更清晰的了解auth的数据表结构情况,下面详细绘制了对应的er图,有图就要更好理解些:

 

 

 

 

 

这样基本就能看出验证权限的过程,每个表通过同颜色的属性进行关联以此进行相应的查询,以此进行权限验证!

 

下面我们再来分析thinkphp的auth类的源码:

Auth类里第一部分是设置受保护的$_config,里面存储的是数组形式的默认auth配置

 

 

[html] view plain copy
 
  1.     //默认配置,如果用户没有在config文件中配置auth的相关配置,将会采用以下默认的配置  
  2.     protected $_config = array(  
  3.         'AUTH_ON'           => true,             // 认证开关  
  4.         'AUTH_TYPE'         => 1,               <span style="white-space:pre;">   </span>// 认证方式,1为实时认证;2为登录认证。  
  5.         'AUTH_GROUP'        => 'auth_group',        // 用户组数据表名  
  6.         'AUTH_GROUP_ACCESS' => 'auth_group_access', <span style="white-space:pre;">  </span>// 用户-用户组关系表  
  7.         'AUTH_RULE'         => 'auth_rule',         // 权限规则表  
  8.         'AUTH_USER'         => 'member'           // 用户信息表  

 

其后是构造函数,构造函数中将上面设置的默认配置变量数组里的auth各个表加上表前缀,然后检查用户有没有在config文件里设置这些值,有的话,就用array_merge覆盖之前的默认配置。

 

[html] view plain copy
 
  1.     public function __construct() {  
  2.         $prefix = C('DB_PREFIX'); //获取数据库表前缀  
  3.         /*将表前缀连接上各自的配置表*/  
  4.         $this->_config['AUTH_GROUP'] = $prefix.$this->_config['AUTH_GROUP'];  
  5.         $this->_config['AUTH_RULE'] = $prefix.$this->_config['AUTH_RULE'];  
  6.         $this->_config['AUTH_USER'] = $prefix.$this->_config['AUTH_USER'];  
  7.         $this->_config['AUTH_GROUP_ACCESS'] = $prefix.$this->_config['AUTH_GROUP_ACCESS'];  
  8.   
  9.   
  10.         /*检查用户有没有配置auth的配置,如果配置了,用array_merge函数将用户配置的auth覆盖默认值,  
  11.         array_merge后面的数组如果键值和前面的数组一样,将会将前面的数组键值对应的值覆盖*/  
  12.         if (C('AUTH_CONFIG')) {  
  13.             //可设置配置项 AUTH_CONFIG, 此配置项为数组。  
  14.             $this->_config = array_merge($this->_config, C('AUTH_CONFIG'));  
  15.         }  
  16.     }  

 

再接着看下面是publick check方法:

首先只我们只看前面一部分,check方法必须传入两个值,后面三个值为可选传入,$name是权限的唯一标识,$uid是用户的id,$type是验证模式,默认为1,也就是时刻认证,$mode是check的模式,$relation是检验$name里面多个权限唯一标识的方式,默认为or,是指多个唯一标识只要满足一个就算成立,如果是and则需要所有的唯一标识全部满足才能成立。

 

[html] view plain copy
 
  1.     /**  
  2.       * 检查权限  
  3.       * @param name string|array  需要验证的规则列表,支持逗号分隔的权限规则或索引数组  
  4.       * @param uid  int           认证用户的id  
  5.       * @param type int           是否开启condition验证  
  6.       * @param string mode        执行check的模式,默认为url,url模式就是index.php?m=mm&c=cc&a=aa这种模式,  
  7.       *                     其他模式就是index.php/mm/cc/aa这种模式  
  8.       * @param relation string    如果为 'or' 表示满足任一条规则即通过验证;如果为 'and'则表示需满足所有规则才能通过验证  
  9.       * @return boolean           通过验证返回true;失败返回false  
  10.      */  
  11.     public function check($name, $uid, $type=1, $mode='url', $relation='or') {  
  12.         /*检查auth配置开关是否开启,如果没有开启则return true这样等于是通过验证了*/  
  13.         if (!$this->_config['AUTH_ON'])  
  14.             return true;  
  15.         /*通过检验auth认证开启后,使用getAuthList方法获取用户需要验证的规则列表*/  
  16.         $authList = $this->getAuthList($uid,$type); //获取用户需要验证的所有有效规则列表  

 

下面我们跳跃到auth类里的getAuthList()方法看一下:

 

[html] view plain copy
 
  1.     /**  
  2.      * 获得权限列表  
  3.      * @param integer $uid  用户id  
  4.      * @param integer $type 认证方式,默认为1,也就是时刻验证,如果为2则是登录验证  
  5.      */  
  6.     /*getAuthList获取用户uid参数以及获取认证方式参数来得到用户的规则列表*/  
  7.     protected function getAuthList($uid,$type) {  
  8.         /*建立static的$_authList的空数组,用以将来保存权限列表*/  
  9.         static $_authList = array(); //保存用户验证通过的权限列表  
  10.   
  11.   
  12.         /*如果$type是数组则转换为用,号隔开的字符串以此保证有不合法的类型*/  
  13.         $t = implode(',',(array)$type);  
  14.   
  15.   
  16.         /*检测$_authList静态变量中保存的权限列表有没有键值为$uid.$t的值,如果有的话证明权限存在且通过,  
  17.         return该值通过验证*/  
  18.         if (isset($_authList[$uid.$t])) {  
  19.             return $_authList[$uid.$t];  
  20.         }  
  21.   
  22.   
  23.         /*检查配置auth_type是否为2登录验证情况系是否存在_AUTH_LIST_.$uid.$t的权限session值,  
  24.         有的话证明已经验证过权限并存在该权限,返回该session值通过验证*/  
  25.         if( $this->_config['AUTH_TYPE']==2 && isset($_SESSION['_AUTH_LIST_'.$uid.$t])){  
  26.             return $_SESSION['_AUTH_LIST_'.$uid.$t];  
  27.         }  
  28.   
  29.   
  30.         /*如果既没有存在$_authList对应的值,也不存在session对应的值,  
  31.         就需要用getGroups来读取用户所属用户组的信息进行查询,该结果为一个数组*/  
  32.         $groups = $this->getGroups($uid);  

下面我们跳到protected getGroups方法里看看:

 

[html] view plain copy
 
  1.     /**  
  2.      * 根据用户id获取用户组,返回值为数组  
  3.      * @param  uid int     用户id  
  4.      * @return array       用户所属的用户组 array(  
  5.      *     array('uid'=>'用户id','group_id'=>'用户组id','title'=>'用户组名称','rules'=>'用户组拥有的规则id,多个,号隔开'),  
  6.      *     ...)     
  7.      */  
  8.       
  9.     public function getGroups($uid) {  
  10.         /*创建static的$groups空数组来存储用户所属的用户组,用户可以有多个用户组,键值用用户的id表示*/  
  11.         static $groups = array();  
  12.   
  13.   
  14.         /*检测$groups键值为用户id的值是否存在,如果存在,则返回该用户所拥有的用户组数组*/  
  15.         if (isset($groups[$uid]))  
  16.             return $groups[$uid];  
  17.   
  18.   
  19.         /*如果$groups键值为用户id的值不存在,新建数据模型  
  20.         以下实际查询的句子等同:  
  21.         select uid,group_id,title,rules from think_auth_group_access a left join  
  22. think_auth_group g on a.group_id=g.id where a.uid=1 and g.status=1;  
  23.         */  
  24.         $user_groups = M()  
  25.         //切换think_auth_group_access表,该表有两个字段,一个用户id,一个对应group_id,该表取了别名a  
  26.             ->table($this->_config['AUTH_GROUP_ACCESS'] . ' a')  
  27.         //查询条件为用户id和think_auth_group_access表里的id相等,同时status=1,也就是状态开启  
  28.             ->where("a.uid='$uid' and g.status='1'")  
  29.         //连接两个表,并设置think_auth_group用户组表别名为g,获取a表group_id=g表的id的行  
  30.             ->join($this->_config['AUTH_GROUP']." g on a.group_id=g.id")  
  31.         //查询条件  
  32.             ->field('uid,group_id,title,rules')->select();  
  33.   
  34.   
  35.   
  36.   
  37.         /*如果上面查询结果不为空,那么把该值赋值给$groups的数组,键值为用户id,否则赋空数组*/  
  38.         $groups[$uid]=$user_groups?:array();  
  39.   
  40.   
  41.         /*将查询到的$groups该用户组的键值对应的权限列表返回*/  
  42.         return $groups[$uid];  
  43.     }  


这里我们假设得到了$groups[$uid],也就是对应用户id的一个或多个用户组数组,每个用户组数组其中包含用户组id,用户组title,以及用户组rules,下面我们回到getAuthList方法,来接着看下面:

 

[html] view plain copy
 
  1. $ids = array();//保存用户所属用户组设置的所有权限规则id  
  2.   
  3. /*将查询到的用户组信息进行循环获取每个用户组权限规则id*/  
  4. foreach ($groups as $g) {  
  5.     //将规则id字符串去除首尾的,号,然后根据,号分割为数组,并将所有用户组的权限id合并组成$ids数组  
  6.     $ids = array_merge($ids, explode(',', trim($g['rules'], ',')));  
  7. }  
  8.   
  9. /*移除$ids里重复的权限id*/  
  10. $ids = array_unique($ids);  
  11.   
  12. /*检测$ids是否为空,如果是则返回权限列表为空数组*/  
  13. if (empty($ids)) {  
  14.     $_authList[$uid.$t] = array();  
  15.     return array();  
  16. }  
  17.   
  18. /*设置搜索条件$map的数组,$map包含所有该用户拥有的规则id以及满足type还有status=1的条件*/  
  19. $map=array(  
  20.     'id'=>array('in',$ids),  
  21.     'type'=>$type,  
  22.     'status'=>1,  
  23. );  
  24.   
  25.   
  26. //根据$map查询到所有规则id所对应的权限规则表的condition和name字段  
  27. /*等同于:select name,condition from thin_auth_rule where id in ($ids) and type=$type and status=1*/  
  28. $rules = M()->table($this->_config['AUTH_RULE'])->where($map)->field('condition,name')->select();  
  29.   
  30. //循环规则,判断结果。  
  31. $authList = array();   //  
  32. foreach ($rules as $rule) {  
  33.     //如果condition字段不为空,根据condition进行验证  
  34.     if (!empty($rule['condition'])) {   
  35.         $user = $this->getUserInfo($uid);//获取用户信息,一维数组  

 

下面要根据condition来验证条件,例如如果用用户的积分来验证,所以这里通过获取一个数组:

 

[html] view plain copy
 
  1. /**  
  2.  * 获得用户资料,根据自己的情况读取数据库  
  3.  * @param $uid 用户id  
  4.  * @return 返回用户的一维数组  
  5.  */  
  6. protected function getUserInfo($uid) {  
  7.     /*设置静态变量$userinfo来保存用户数组,先设定空数组*/  
  8.     static $userinfo=array();  
  9.     /*检查静态变量$userinfo有没有键值为用户id的对应值,有的话返回该值,  
  10.     没有进行的话读取数据库查询然后返回该值*/  
  11.     if(!isset($userinfo[$uid])){  
  12.         /*这里相当于:  
  13.             select * from think_user where uid=$uid;  
  14.             因此需要根据自己的情况来更改查询的情况,例如如果condition查询的是积分字段,  
  15.             假设积分字段为score,用户id字段为id,那么应该更改为:  
  16.             $userinfo[$uid]=M()->field('score')->where(array('id'=>$uid))->table($this->_config['AUTH_USER'])->find();  
  17.         */  
  18.          $userinfo[$uid]=M()->where(array('uid'=>$uid))->table($this->_config['AUTH_USER'])->find();  
  19.     }  
  20.     return $userinfo[$uid];  
  21. }  

 

如上面的假设我们得到的$userinfo[$uid]则会是数组,condition字段里面放着该用户的积分字段score的查询结果!

 

下面再回到getAuthList方法里面,我们接着看:

 

[html] view plain copy
 
  1.         /*假设条件的格式为{score}>50,这里使用preg_replace替换成$user['score']>50  
  2.         并赋值给$command,更具体的可以参考preg_match函数以及正则表达式*/  
  3.         $command = preg_replace('/\{(\w*?)\}/', '$user[\'\\1\']', $rule['condition']);  
  4.   
  5.         //执行$condition的赋值  
  6.         @(eval('$condition=(' . $command . ');'));  
  7.   
  8.         /*如果condition变量存在,那么将全部转为小写的$rule['name']也就是权限的唯一标识赋值给$authList[]数组*/  
  9.         if ($condition) {  
  10.             $authList[] = strtolower($rule['name']);  
  11.         }  
  12.     } else {  
  13.         //由于condition字段为空,因此存在权限便将其赋值给$authList[]数组  
  14.         $authList[] = strtolower($rule['name']);  
  15.     }  
  16. }  
  17.   
  18. /*将上面循环得到的权限唯一标识数组$authList赋值给$_authList[$uid.$t]以备下次使用*/  
  19. $_authList[$uid.$t] = $authList;  
  20.   
  21. /*如果配置中的type为2,那么$authList赋值给session*/  
  22. if($this->_config['AUTH_TYPE']==2){  
  23.     //规则列表结果保存到session里面,以备下次验证使用  
  24.     $_SESSION['_AUTH_LIST_'.$uid.$t]=$authList;  
  25. }  
  26. /*返回去掉重复的$authList*/  
  27. return array_unique($authList);  


得到返回的权限后,我们重新回到check方法:

 

[html] view plain copy
 
  1. /*检查传递进来的$name是否是个字符串*/  
  2. if (is_string($name)) {  
  3.     /*转换为小写*/  
  4.     $name = strtolower($name);  
  5.     /*获取第一个,出现的位置,如果不为false*/  
  6.     if (strpos($name, ',') !== false) {  
  7.         /*根据,号分解为数组*/  
  8.         $name = explode(',', $name);  
  9.     } else {  
  10.         /*不存在,号分割的$name则转换为数组*/  
  11.         $name = array($name);  
  12.     }  
  13. }  

这样我们就得到了传递进来的唯一标识数组

 

[html] view plain copy
 
  1. /*设置$list为一个空数组,用以保存通过验证的规则名*/  
  2. $list = array(); //保存验证通过的规则名  
  3. /*检查$mode*/  
  4. if ($mode=='url') {  
  5.     /*获取序列化和转变小写再非序列化后的$_REQUEST数组$REQUEST,  
  6.     该数组里现在有了url里的传值数组*/  
  7.     $REQUEST = unserialize( strtolower(serialize($_REQUEST)) );  
  8. }  
  9. /*循环$authList的权限规则*/  
  10. foreach ( $authList as $auth ) {  
  11.     /*替换掉开始到?的部分*/  
  12.     $query = preg_replace('/^.+\?/U','',$auth);  
  13.     /*如果替换掉的$query不等于$auth,同时$mode是url模式,证明内部带着参数*/  
  14.     if ($mode=='url' && $query!=$auth ) {  
  15.         /*将$query的参数解析到$param里面去*/  
  16.         parse_str($query,$param); //解析规则中的param  
  17.         /*比较两者参数的交集*/  
  18.         $intersect = array_intersect_assoc($REQUEST,$param);  
  19.         /*将以问号后面跟任意个任意字符结尾的替换成空值*/  
  20.         $auth = preg_replace('/\?.*$/U','',$auth);  
  21.         if ( in_array($auth,$name) && $intersect==$param ) {  //如果节点相符且url参数满足  
  22.             $list[] = $auth ;  
  23.         }  
  24.     /*如果check的模式不等于url或者等于url同时$query和$auth相同,  
  25.     在这个基础上,如果传进来的$name唯一标识符数组里有该层循环的权限规则  
  26.     把该权限规则存入通过认证权限的$list数组里  
  27.     */   
  28.     }else if (in_array($auth , $name)){  
  29.         $list[] = $auth ;  
  30.     }  
  31. }  
  32. /*如果$relation是or参数,认证的权限数组不为空,那么返回true*/  
  33. if ($relation == 'or' and !empty($list)) {  
  34.     return true;  
  35. }  
  36. /*对比$name中有没有$list里没有的,有的话存入$diff变量中*/  
  37. $diff = array_diff($name, $list);  
  38. /*如果$relation是and,他那个是$diff是空值,那么所有的唯一标识都通过了验证,  
  39. 则返回true*/  
  40. if ($relation == 'and' and empty($diff)) {  
  41.     return true;  
  42. }  
  43. return false;  



以上是整体auth的源码分析!

 

下面来看一个实例,表的内容就是上面截图的,下面后改动会红字标出

 

[html] view plain copy
 
  1. class IndexController extends Controller {  
  2.     public function ceshi(){  
  3.         $auth=new \Think\Auth();  
  4.         $a=$auth->check('Index/index,Index/add,Index/delete',1,$type=1, $mode='url', $relation='or');  
  5.         dump($a);  
  6.     }  
  7. }  


这里三个唯一标识都在用户组1中,而用户id为1的用户组为1,验证关系用的or,就是满足一个唯一标识即可,不考虑条件的情况下这个肯定认证通过,但是type为1,必须对其验证,这个时候如果数据库think_auth_rule的type不为1的话会验证失败,直接返回false,一定要注意,这里的type一定要和数据库里的type一致。

 

如果把$relation改为and会怎么样呢,验证会通过返回true,因为除开Index/index里有条件设置外,其他都为空,所以存在该权限规则就算通过,而Index/index的积分要求10以上,用户的积分是50,是满足条件的,所以三个权限规则都通过了,返回true。

 

如果是将Index/index的权限规则改为{score}>60,这个时候就会返回false,虽然其他两个权限规则通过了,但是Index/index规则没有通过,因为积分不足。

 

可能有些地方讲解的还不尽人意,以后再进行相应的补充,看了源码后觉得auth类在自己用的时候可能需要进行一定的改动方便自己使用会比较好。

posted @ 2018-01-03 11:04  袁浩浩  阅读(326)  评论(0编辑  收藏  举报