入门客AI创业平台(我带你入门,你带我飞行)
博文笔记

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

创建时间:2017-02-26 投稿人: 浏览次数:133

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配置

    //默认配置,如果用户没有在config文件中配置auth的相关配置,将会采用以下默认的配置
    protected $_config = array(
        "AUTH_ON"           => true,             // 认证开关
        "AUTH_TYPE"         => 1,               	// 认证方式,1为实时认证;2为登录认证。
        "AUTH_GROUP"        => "auth_group",        // 用户组数据表名
        "AUTH_GROUP_ACCESS" => "auth_group_access", 	// 用户-用户组关系表
        "AUTH_RULE"         => "auth_rule",         // 权限规则表
        "AUTH_USER"         => "member"           // 用户信息表

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

    public function __construct() {
        $prefix = C("DB_PREFIX"); //获取数据库表前缀
        /*将表前缀连接上各自的配置表*/
        $this->_config["AUTH_GROUP"] = $prefix.$this->_config["AUTH_GROUP"];
        $this->_config["AUTH_RULE"] = $prefix.$this->_config["AUTH_RULE"];
        $this->_config["AUTH_USER"] = $prefix.$this->_config["AUTH_USER"];
        $this->_config["AUTH_GROUP_ACCESS"] = $prefix.$this->_config["AUTH_GROUP_ACCESS"];


        /*检查用户有没有配置auth的配置,如果配置了,用array_merge函数将用户配置的auth覆盖默认值,
        array_merge后面的数组如果键值和前面的数组一样,将会将前面的数组键值对应的值覆盖*/
        if (C("AUTH_CONFIG")) {
            //可设置配置项 AUTH_CONFIG, 此配置项为数组。
            $this->_config = array_merge($this->_config, C("AUTH_CONFIG"));
        }
    }

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

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

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


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


    /**
     * 获得权限列表
     * @param integer $uid  用户id
     * @param integer $type 认证方式,默认为1,也就是时刻验证,如果为2则是登录验证
     */
    /*getAuthList获取用户uid参数以及获取认证方式参数来得到用户的规则列表*/
    protected function getAuthList($uid,$type) {
        /*建立static的$_authList的空数组,用以将来保存权限列表*/
        static $_authList = array(); //保存用户验证通过的权限列表


        /*如果$type是数组则转换为用,号隔开的字符串以此保证有不合法的类型*/
        $t = implode(",",(array)$type);


        /*检测$_authList静态变量中保存的权限列表有没有键值为$uid.$t的值,如果有的话证明权限存在且通过,
        return该值通过验证*/
        if (isset($_authList[$uid.$t])) {
            return $_authList[$uid.$t];
        }


        /*检查配置auth_type是否为2登录验证情况系是否存在_AUTH_LIST_.$uid.$t的权限session值,
        有的话证明已经验证过权限并存在该权限,返回该session值通过验证*/
        if( $this->_config["AUTH_TYPE"]==2 && isset($_SESSION["_AUTH_LIST_".$uid.$t])){
            return $_SESSION["_AUTH_LIST_".$uid.$t];
        }


        /*如果既没有存在$_authList对应的值,也不存在session对应的值,
        就需要用getGroups来读取用户所属用户组的信息进行查询,该结果为一个数组*/
        $groups = $this->getGroups($uid);

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

    /**
     * 根据用户id获取用户组,返回值为数组
     * @param  uid int     用户id
     * @return array       用户所属的用户组 array(
     *     array("uid"=>"用户id","group_id"=>"用户组id","title"=>"用户组名称","rules"=>"用户组拥有的规则id,多个,号隔开"),
     *     ...)   
     */
    
    public function getGroups($uid) {
        /*创建static的$groups空数组来存储用户所属的用户组,用户可以有多个用户组,键值用用户的id表示*/
        static $groups = array();


        /*检测$groups键值为用户id的值是否存在,如果存在,则返回该用户所拥有的用户组数组*/
        if (isset($groups[$uid]))
            return $groups[$uid];


        /*如果$groups键值为用户id的值不存在,新建数据模型
        以下实际查询的句子等同:
        select uid,group_id,title,rules from think_auth_group_access a left join
think_auth_group g on a.group_id=g.id where a.uid=1 and g.status=1;
        */
        $user_groups = M()
        //切换think_auth_group_access表,该表有两个字段,一个用户id,一个对应group_id,该表取了别名a
            ->table($this->_config["AUTH_GROUP_ACCESS"] . " a")
        //查询条件为用户id和think_auth_group_access表里的id相等,同时status=1,也就是状态开启
            ->where("a.uid="$uid" and g.status="1"")
        //连接两个表,并设置think_auth_group用户组表别名为g,获取a表group_id=g表的id的行
            ->join($this->_config["AUTH_GROUP"]." g on a.group_id=g.id")
        //查询条件
            ->field("uid,group_id,title,rules")->select();




        /*如果上面查询结果不为空,那么把该值赋值给$groups的数组,键值为用户id,否则赋空数组*/
        $groups[$uid]=$user_groups?:array();


        /*将查询到的$groups该用户组的键值对应的权限列表返回*/
        return $groups[$uid];
    }

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

        $ids = array();//保存用户所属用户组设置的所有权限规则id

        /*将查询到的用户组信息进行循环获取每个用户组权限规则id*/
        foreach ($groups as $g) {
            //将规则id字符串去除首尾的,号,然后根据,号分割为数组,并将所有用户组的权限id合并组成$ids数组
            $ids = array_merge($ids, explode(",", trim($g["rules"], ",")));
        }

        /*移除$ids里重复的权限id*/
        $ids = array_unique($ids);

        /*检测$ids是否为空,如果是则返回权限列表为空数组*/
        if (empty($ids)) {
            $_authList[$uid.$t] = array();
            return array();
        }

        /*设置搜索条件$map的数组,$map包含所有该用户拥有的规则id以及满足type还有status=1的条件*/
        $map=array(
            "id"=>array("in",$ids),
            "type"=>$type,
            "status"=>1,
        );


        //根据$map查询到所有规则id所对应的权限规则表的condition和name字段
        /*等同于:select name,condition from thin_auth_rule where id in ($ids) and type=$type and status=1*/
        $rules = M()->table($this->_config["AUTH_RULE"])->where($map)->field("condition,name")->select();

        //循环规则,判断结果。
        $authList = array();   //
        foreach ($rules as $rule) {
            //如果condition字段不为空,根据condition进行验证
            if (!empty($rule["condition"])) { 
                $user = $this->getUserInfo($uid);//获取用户信息,一维数组


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

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


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


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

                /*假设条件的格式为{score}>50,这里使用preg_replace替换成$user["score"]>50
                并赋值给$command,更具体的可以参考preg_match函数以及正则表达式*/
                $command = preg_replace("/{(w*?)}/", "$user["\1"]", $rule["condition"]);

                //执行$condition的赋值
                @(eval("$condition=(" . $command . ");"));

                /*如果condition变量存在,那么将全部转为小写的$rule["name"]也就是权限的唯一标识赋值给$authList[]数组*/
                if ($condition) {
                    $authList[] = strtolower($rule["name"]);
                }
            } else {
                //由于condition字段为空,因此存在权限便将其赋值给$authList[]数组
                $authList[] = strtolower($rule["name"]);
            }
        }

        /*将上面循环得到的权限唯一标识数组$authList赋值给$_authList[$uid.$t]以备下次使用*/
        $_authList[$uid.$t] = $authList;
        
        /*如果配置中的type为2,那么$authList赋值给session*/
        if($this->_config["AUTH_TYPE"]==2){
            //规则列表结果保存到session里面,以备下次验证使用
            $_SESSION["_AUTH_LIST_".$uid.$t]=$authList;
        }
        /*返回去掉重复的$authList*/
        return array_unique($authList);
    }

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

        /*检查传递进来的$name是否是个字符串*/
        if (is_string($name)) {
            /*转换为小写*/
            $name = strtolower($name);
            /*获取第一个,出现的位置,如果不为false*/
            if (strpos($name, ",") !== false) {
                /*根据,号分解为数组*/
                $name = explode(",", $name);
            } else {
                /*不存在,号分割的$name则转换为数组*/
                $name = array($name);
            }
        }
这样我们就得到了传递进来的唯一标识数组

        /*设置$list为一个空数组,用以保存通过验证的规则名*/
        $list = array(); //保存验证通过的规则名
        /*检查$mode*/
        if ($mode=="url") {
            /*获取序列化和转变小写再非序列化后的$_REQUEST数组$REQUEST,
            该数组里现在有了url里的传值数组*/
            $REQUEST = unserialize( strtolower(serialize($_REQUEST)) );
        }
        /*循环$authList的权限规则*/
        foreach ( $authList as $auth ) {
            /*替换掉开始到?的部分*/
            $query = preg_replace("/^.+?/U","",$auth);
            /*如果替换掉的$query不等于$auth,同时$mode是url模式,证明内部带着参数*/
            if ($mode=="url" && $query!=$auth ) {
                /*将$query的参数解析到$param里面去*/
                parse_str($query,$param); //解析规则中的param
                /*比较两者参数的交集*/
                $intersect = array_intersect_assoc($REQUEST,$param);
                /*将以问号后面跟任意个任意字符结尾的替换成空值*/
                $auth = preg_replace("/?.*$/U","",$auth);
                if ( in_array($auth,$name) && $intersect==$param ) {  //如果节点相符且url参数满足
                    $list[] = $auth ;
                }
            /*如果check的模式不等于url或者等于url同时$query和$auth相同,
            在这个基础上,如果传进来的$name唯一标识符数组里有该层循环的权限规则
            把该权限规则存入通过认证权限的$list数组里
            */ 
            }else if (in_array($auth , $name)){
                $list[] = $auth ;
            }
        }
        /*如果$relation是or参数,认证的权限数组不为空,那么返回true*/
        if ($relation == "or" and !empty($list)) {
            return true;
        }
        /*对比$name中有没有$list里没有的,有的话存入$diff变量中*/
        $diff = array_diff($name, $list);
        /*如果$relation是and,他那个是$diff是空值,那么所有的唯一标识都通过了验证,
        则返回true*/
        if ($relation == "and" and empty($diff)) {
            return true;
        }
        return false;
    }


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


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

class IndexController extends Controller {
	public function ceshi(){
        $auth=new ThinkAuth();
        $a=$auth->check("Index/index,Index/add,Index/delete",1,$type=1, $mode="url", $relation="or");
        dump($a);
	}
}

这里三个唯一标识都在用户组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类在自己用的时候可能需要进行一定的改动方便自己使用会比较好。
















声明:该文观点仅代表作者本人,入门客AI创业平台信息发布平台仅提供信息存储空间服务,如有疑问请联系rumenke@qq.com。