这个权限类,很通用,包含了权限认证的基本思路,彻底理解逻辑后,可以用于其他系统的开发,所以理解清楚很有必要.
下面是逐行代码级的详细解释:
```
<?php
// +----------------------------------------------------------------------
// | ThinkPHP \[ WE CAN DO IT JUST THINK IT \]
// +----------------------------------------------------------------------
// | Copyright (c) 2011 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: luofei614
// +----------------------------------------------------------------------
// | 修改者: anuo (本权限类在原3.2.3的基础上修改过来的)
// +----------------------------------------------------------------------
namespace fast;
use think\\Db;
use think\\Config;
use think\\Session;
use think\\Request;
/\*\*
\* 权限认证类
\* 功能特性:
\* 1,是对规则进行认证,不是对节点进行认证。用户可以把节点当作规则名称实现对节点进行认证。
\* $auth=new Auth(); $auth->check('规则名称','用户id')
\* 2,可以同时对多条规则进行认证,并设置多条规则的关系(or或者and)
\* $auth=new Auth(); $auth->check('规则1,规则2','用户id','and')
\* 第三个参数为and时表示,用户需要同时具有规则1和规则2的权限。 当第三个参数为or时,表示用户值需要具备其中一个条件即可。默认为or
\* 3,一个用户可以属于多个用户组(think\_auth\_group\_access表 定义了用户所属用户组)。我们需要设置每个用户组拥有哪些规则(think\_auth\_group 定义了用户组权限)
\* 4,支持规则表达式。
\* 在think\_auth\_rule 表中定义一条规则,condition字段就可以定义规则表达式。 如定义{score}>5 and {score}<100
\* 表示用户的分数在5-100之间时这条规则才会通过。
\*/
class Auth
{
/\*\*
\* @var object 对象实例
\*/
protected static $instance;
protected $rules = \[\];
/\*\*
\* 当前请求实例
\* @var Request
\*/
protected $request;
//默认配置
protected $config = \[
'auth\_on' => 1, // 权限开关
'auth\_type' => 1, // 认证方式,1为实时认证;2为登录认证。
'auth\_group' => 'auth\_group', // 用户组数据表
'auth\_group\_access' => 'auth\_group\_access', // 用户-用户组关系表
'auth\_rule' => 'auth\_rule', // 权限规则表
'auth\_user' => 'user', // 用户表
\];
public function \_\_construct()
{
if ($auth = Config::get('auth')) {//也可以在config.php中进行配置,会覆盖掉默认配置
$this\->config = array\_merge($this\->config, $auth);
}
// 初始化request
$this\->request = Request::instance();
}
/\*\*
\* 初始化
\* 在整个PHP进程中只生成一个实例,这样可以节约内存和加快速度,不用反复生成新的实例.这是一种编程技巧
\* @access public
\* @param array $options 参数
\* @return Auth
\*/
public static function instance($options = \[\])
{
if (is\_null(self::$instance)) {
self::$instance = new static($options);
}
return self::$instance;
}
/\*\*
\* 检查权限
\* @param string|array $name 需要验证的规则列表,同时验证多条规则时,支持逗号分隔的权限规则或索引数组
\* @param int $uid 认证用户的id
\* @param string $relation 如果为 'or' 表示满足任一条规则即通过验证;如果为 'and'则表示需满足所有规则才能通过验证
\* @param string $mode 执行验证的模式,可分为url,normal ,url模式会同时比较权限路径和参数
\* @return bool 通过验证返回true;失败返回false
\*/
public function check($name, $uid, $relation = 'or', $mode = 'url')
{
if (!$this\->config\['auth\_on'\]) {
//没有开启权限认证,直接返回通过
return true;
}
// 获取用户需要验证的所有有效规则列表
$rulelist = $this\->getRuleList($uid);
if (in\_array('\*', $rulelist)) {
//\*表示全部权限,一般只有超管组才有这个标识
return true;
}
if (is\_string($name)) {
$name = strtolower($name);
if (strpos($name, ',') !== false) {
$name = explode(',', $name);
} else {
$name = \[$name\];
}
}
$list = \[\]; //保存验证通过的规则名
if ('url' == $mode) {
$REQUEST = unserialize(strtolower(serialize($this\->request\->param())));
}
foreach ($rulelist as $rule) {
$query = preg\_replace('/^.+\\?/U', '', $rule);
//正则解释:以任意字符开头的,然后加上问号?结尾,U:非贪婪模式,也就是尽可能短的匹配,这里就是只匹配第一个?问号前面的 ,把这些替换成空,
//例子:string(24) "user/info?name=jack&id=1" 结果: string(14) "name=jack&id=1"
//得出来的结果与原规则比较,如果相同,也就是原来的规则并不是url,因为没有带问号,那么就不是url模式
if ('url' == $mode && $query != $rule) {
parse\_str($query, $param); //解析规则中的param ,把query 解析到变量param中,param是一个数组
$intersect = array\_intersect\_assoc($REQUEST, $param);//带索引检查计算数组的交集,也就是把相同的索引和值另外形成一个数组
$rule = preg\_replace('/\\?.\*$/U', '', $rule);//这里得到是问号前面那段内容,也就是:string(9) "user/info", 与上面的结果相反
if (in\_array($rule, $name) && $intersect == $param) {
//如果节点相符且url参数满足
$list\[\] = $rule;
}
} else {
if (in\_array($rule, $name)) {
$list\[\] = $rule;
}
}
}
if ('or' == $relation && !empty($list)) {
//如果是或的关系,那么只要通过了任何一条规则,就算是有权限了
return true;
}
$diff = array\_diff($name, $list);//返回在 name数组 中但是不在其他数组里的值
if ('and' == $relation && empty($diff)) {
//如果是且的关系,那么就必须name里面的全部规则都必须在用户的已经验证通过的规则数组里面
return true;
}
return false;
}
/\*\*
\* 根据用户id获取用户组,返回值为数组
\* @param int $uid 用户id
\* @return array 用户所属的用户组 array(
\* array('uid'=>'用户id','group\_id'=>'用户组id','name'=>'用户组名称','rules'=>'用户组拥有的规则id,多个,号隔开'),
\* ...)
\*/
public function getGroups($uid)
{
static $groups = \[\];
if (isset($groups\[$uid\])) {
return $groups\[$uid\];//已经查过了用户组,存在了就直接返回
}
// 执行查询
$user\_groups = Db::name($this\->config\['auth\_group\_access'\])
->alias('aga')
->join('\_\_' . strtoupper($this\->config\['auth\_group'\]) . '\_\_ ag', 'aga.group\_id = ag.id', 'LEFT')
->field('aga.uid,aga.group\_id,ag.id,ag.pid,ag.name,ag.rules')
->where("aga.uid='{$uid}' and ag.status='normal'")
->select();
//join里面的'\_\_表名\_\_'会被转换成带前缀的完整表明,具体函数是thinkphp\\library\\think\\db\\Query.php 的 public function parseSqlTable($sql)
$groups\[$uid\] = $user\_groups ?: \[\];
return $groups\[$uid\];
}
/\*\*
\* 获得权限规则列表
\* @param int $uid 用户id
\* @return array
\*/
public function getRuleList($uid)
{
static $\_rulelist = \[\]; //保存用户验证通过的权限列表,这里保存为静态变量,可以节约大量重复查询的操作,虽然有可能在运行的同时被修改权限,但这个时间太短暂,可以忽略不计.
//但如果是使用swoole的话,就需要考虑静态变量的值与数据库同步更新的问题了
if (isset($\_rulelist\[$uid\])) {
return $\_rulelist\[$uid\];
}
//1为实时认证;2为登录认证
if (2 == $this\->config\['auth\_type'\] && Session::has('\_rule\_list\_' . $uid)) {
//如果是只在登录时, 查一次权限,可以避免每次都实时去查数据库,但同时有可能值和数据库不一致!也就是在登录后权限被管理员修改了,这样无法做到权限修改实时生效
return Session::get('\_rule\_list\_' . $uid);
}
// 读取用户规则节点
$ids = $this\->getRuleIds($uid);
if (empty($ids)) {
$\_rulelist\[$uid\] = \[\];
return \[\];
}
// 筛选条件
$where = \[
'status' => 'normal'
\];
if (!in\_array('\*', $ids)) {
$where\['id'\] = \['in', $ids\];
}
//读取用户组所有权限规则
$this\->rules = Db::name($this\->config\['auth\_rule'\])->where($where)->field('id,pid,condition,icon,name,title,ismenu')->select();
//循环规则,判断结果。
$rulelist = \[\]; //
if (in\_array('\*', $ids)) {
$rulelist\[\] = "\*";
}
foreach ($this\->rules as $rule) {
//超级管理员无需验证condition:额外的验证条件
if (!empty($rule\['condition'\]) && !in\_array('\*', $ids)) {
//根据condition进行验证
$user = $this\->getUserInfo($uid); //获取用户信息,一维数组
$nums = 0;
$condition = str\_replace(\['&&', '||'\], "\\r\\n", $rule\['condition'\]);//把&& 和 || 替换成换行符
$condition = preg\_replace('/\\{(\\w\*?)\\}/', '\\\\1', $condition);//搜索condition中符合正则的内容,替换成结果集中的第二个结果,具体看官方文档:https://www.php.net/manual/zh/function.preg-replace.php
//正则解释:/\\{(\\w\*?)\\}/:包裹在{}中的,零次或一次或多次的 单词
//正则解释'\\\\1':结果集中的第二个结果,就是{}里面的内容,等于是去除了{}
//这里去除{}的目的是因为条件的写法是:{score}>5 and {score}<100 ,变量名由{}包裹,为了下面的进一步处理,需要先去掉{}
$conditionArr = explode("\\r\\n", $condition);
foreach ($conditionArr as $index => $item) {
preg\_match("/^(\\w+)\\s?(\[\\>\\<\\=\]+)\\s?(.\*)$/", trim($item), $matches);
//正则解释:^(\\w+):以 一次或多次 单词 开头,
//正则解释: \\s? : 零个或一个空白字符,这里其实指的空格 ,
//正则解释: (\[\\>\\<\\=\]+) : 一次或多次 大于或小于或等于号,
//正则解释:(.\*)$ : 点表示任意一个字符(不包含换行符),\*表示任意多个,$表示以什么结尾. 整体意思就是 以任意不是换行符的内容结尾
//那么能够匹配这个正则的内容的形式应该是: a = 1 或者没有空格的 name=jack
if ($matches && isset($user\[$matches\[1\]\]) && version\_compare($user\[$matches\[1\]\], $matches\[3\], $matches\[2\])) {
//matches是匹配出来的结果集,以name=jack为例:元素0:是整个结果name=jack,1:name,2:=,3:jack
//这里使用版本比较version\_compare的方式,来得出用户的值是否满足条件,是一种巧妙的用法,因为本身这个函数的目的不是这个用法..
$nums++;
}
}
if ($conditionArr && ((stripos($rule\['condition'\], "||") !== false && $nums > 0) || count($conditionArr) == $nums)) {
//如果是或||,只需要nums有一个就算是拥有这个权限,否则就是且&&,必须nums的值与条件数量相等,也就全部符合才算拥有这个权限
$rulelist\[$rule\['id'\]\] = strtolower($rule\['name'\]);
}
} else {
//没有额外的验证条件,只要存在就记录
$rulelist\[$rule\['id'\]\] = strtolower($rule\['name'\]);
}
}
$\_rulelist\[$uid\] = $rulelist;
//登录验证则需要保存规则列表
if (2 == $this\->config\['auth\_type'\]) {
//规则列表结果保存到session
Session::set('\_rule\_list\_' . $uid, $rulelist);
}
return array\_unique($rulelist);
}
public function getRuleIds($uid)
{
//读取用户所属用户组
$groups = $this\->getGroups($uid);
$ids = \[\]; //保存用户所属用户组设置的所有权限规则id
foreach ($groups as $g) {
//一个用户可能同时拥有多个用户组,这里把全部组的权限id合并起来
$ids = array\_merge($ids, explode(',', trim($g\['rules'\], ',')));
}
$ids = array\_unique($ids);//把重复的权限id去除
return $ids;
}
/\*\*
\* 获得用户资料
\* @param int $uid 用户id
\* @return mixed
\*/
protected function getUserInfo($uid)
{
static $user\_info = \[\];
$user = Db::name($this\->config\['auth\_user'\]);
// 获取用户表主键
$\_pk = is\_string($user\->getPk()) ? $user\->getPk() : 'uid';
if (!isset($user\_info\[$uid\])) {
$user\_info\[$uid\] = $user\->where($\_pk, $uid)->find();
}
return $user\_info\[$uid\];
}
}
```
- FA的JS调用机制说明
- FA的JS之Fast.api逐个详解
- FA页面渲染时后端传递数据给前端的方式
- FA的ajax查询数据的前后台流程
- FA特有的函数解释
- FA的鉴权Auth类
- extend\fast\Auth.php详解
- application\admin\library\Auth.php详解
- application\common\library\Auth.php详解
- FA的Token机制
- FA管理员(后台)的权限机制
- FA用户(前台和API)的权限机制
- FA在前台模板文件中进行鉴权
- FA的登录页面
- TP类Hook:钩子机制
- TP类Lang:多语言机制
- TP类Config:参数配置机制
- TP类Request:请求类
- TP的模型关联详解
- think-queue队列组件
- Queue.php
- \queue\Connector.php
- \queue\connector\Redis.php
- \queue\Job.php
- queue\job\Redis.php
- PHP规则:正则表达式
- PHP规则:闭包与匿名函数
- 项目架构说明
- 代码架构
- TP数据库where条件的各种写法
