ThinkPHP权限认证Auth实例详解


Posted in PHP onJuly 22, 2014

本文以实例代码的形式深入剖析了ThinkPHP权限认证Auth的实现原理与方法,具体步骤如下:

mysql数据库部分sql代码:

-- ----------------------------
-- Table structure for think_auth_group
-- ----------------------------
DROP TABLE IF EXISTS `think_auth_group`;
CREATE TABLE `think_auth_group` (
 `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
 `title` char(100) NOT NULL DEFAULT '',
 `status` tinyint(1) NOT NULL DEFAULT '1',
 `rules` char(80) NOT NULL DEFAULT '',
 PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='用户组表';

-- ----------------------------
-- Records of think_auth_group
-- ----------------------------
INSERT INTO `think_auth_group` VALUES ('1', '管理组', '1', '1,2');

-- ----------------------------
-- Table structure for think_auth_group_access
-- ----------------------------
DROP TABLE IF EXISTS `think_auth_group_access`;
CREATE TABLE `think_auth_group_access` (
 `uid` mediumint(8) unsigned NOT NULL COMMENT '用户id',
 `group_id` mediumint(8) unsigned NOT NULL COMMENT '用户组id',
 UNIQUE KEY `uid_group_id` (`uid`,`group_id`),
 KEY `uid` (`uid`),
 KEY `group_id` (`group_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='用户组明细表';

-- ----------------------------
-- Records of think_auth_group_access
-- ----------------------------
INSERT INTO `think_auth_group_access` VALUES ('1', '1');
INSERT INTO `think_auth_group_access` VALUES ('1', '2');

-- ----------------------------
-- Table structure for think_auth_rule
-- ----------------------------
DROP TABLE IF EXISTS `think_auth_rule`;
CREATE TABLE `think_auth_rule` (
 `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
 `name` char(80) NOT NULL DEFAULT '' COMMENT '规则唯一标识',
 `title` char(20) NOT NULL DEFAULT '' COMMENT '规则中文名称',
 `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态:为1正常,为0禁用',
 `type` char(80) NOT NULL,
 `condition` char(100) NOT NULL DEFAULT '' COMMENT '规则表达式,为空表示存在就验证,不为空表示按照条件验证',
 PRIMARY KEY (`id`),
 UNIQUE KEY `name` (`name`)
) ENGINE=MyISAM AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COMMENT='规则表';

-- ----------------------------
-- Records of think_auth_rule
-- ----------------------------
INSERT INTO `think_auth_rule` VALUES ('1', 'Home/index', '列表', '1', 'Home', '');
INSERT INTO `think_auth_rule` VALUES ('2', 'Home/add', '添加', '1', 'Home', '');
INSERT INTO `think_auth_rule` VALUES ('3', 'Home/edit', '编辑', '1', 'Home', '');
INSERT INTO `think_auth_rule` VALUES ('4', 'Home/delete', '删除', '1', 'Home', '');


DROP TABLE IF EXISTS `think_user`;
CREATE TABLE `think_user` (
 `id` int(11) NOT NULL,
 `username` varchar(30) DEFAULT NULL,
 `password` varchar(32) DEFAULT NULL,
 `age` tinyint(2) DEFAULT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of think_user
-- ----------------------------
INSERT INTO `think_user` VALUES ('1', 'admin', '21232f297a57a5a743894a0e4a801fc3', '25');

配置文件Application\Common\Conf\config.php部分:

<?php

return array(
 //'配置项'=>'配置值'
 'DB_DSN' => '', // 数据库连接DSN 用于PDO方式
 'DB_TYPE' => 'mysql', // 数据库类型
 'DB_HOST' => 'localhost', // 服务器地址
 'DB_NAME' => 'thinkphp', // 数据库名
 'DB_USER' => 'root', // 用户名
 'DB_PWD' => 'root', // 密码
 'DB_PORT' => 3306, // 端口
 'DB_PREFIX' => 'think_', // 数据库表前缀 
 
 'AUTH_CONFIG' => array(
  'AUTH_ON' => true, //认证开关
  'AUTH_TYPE' => 1, // 认证方式,1为时时认证;2为登录认证。
  'AUTH_GROUP' => 'think_auth_group', //用户组数据表名
  'AUTH_GROUP_ACCESS' => 'think_auth_group_access', //用户组明细表
  'AUTH_RULE' => 'think_auth_rule', //权限规则表
  'AUTH_USER' => 'think_user'//用户信息表
 )
);

项目Home控制器部分Application\Home\Controller\IndexController.class.php代码:

<?php
namespace Home\Controller;
use Think\Controller;
class IndexController extends Controller {
 public function index() {
  $Auth = new \Think\Auth();
  //需要验证的规则列表,支持逗号分隔的权限规则或索引数组
  $name = MODULE_NAME . '/' . ACTION_NAME;
  //当前用户id
  $uid = '1';
  //分类
  $type = MODULE_NAME;
  //执行check的模式
  $mode = 'url';
  //'or' 表示满足任一条规则即通过验证;
  //'and'则表示需满足所有规则才能通过验证
  $relation = 'and';
  if ($Auth->check($name, $uid, $type, $mode, $relation)) {
   die('认证:成功');
  } else {
   die('认证:失败');
  }
 }
}

以上这些代码就是最基本的验证代码示例。

下面是源码阅读:

1、权限检验类初始化配置信息:

$Auth = new \Think\Auth();

创建一个对象时程序会合并配置信息
程序会合并Application\Common\Conf\config.php中的AUTH_CONFIG数组

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'];
  if (C('AUTH_CONFIG')) {
   //可设置配置项 AUTH_CONFIG, 此配置项为数组。
   $this->_config = array_merge($this->_config, C('AUTH_CONFIG'));
  }
 }

2、检查权限:

check($name, $uid, $type = 1, $mode = 'url', $relation = 'or')

大体分析一下这个方法

首先判断是否关闭权限校验 如果配置信息AUTH_ON=>false 则不会进行权限验证 否则继续验证权限

if (!$this->_config['AUTH_ON']) {
 return true;
}

获取权限列表之后会详细介绍:

$authList = $this->getAuthList($uid, $type);

此次需要验证的规则列表转换成数组:

if (is_string($name)) {
 $name = strtolower($name);
 if (strpos($name, ',') !== false) {
 $name = explode(',', $name);
 } else {
 $name = array($name);
 }
}

所以$name参数是不区分大小写的,最终都会转换成小写

开启url模式时全部转换为小写:

if ($mode == 'url') {
 $REQUEST = unserialize(strtolower(serialize($_REQUEST)));
}

权限校验核心代码段之一,即循环所有该用户权限 判断 当前需要验证的权限 是否 在用户授权列表中:

foreach ($authList as $auth) {
 $query = preg_replace('/^.+\?/U', '', $auth);//获取url参数
 if ($mode == 'url' && $query != $auth) {
 parse_str($query, $param); //获取数组形式url参数
 $intersect = array_intersect_assoc($REQUEST, $param);
 $auth = preg_replace('/\?.*$/U', '', $auth);//获取访问的url文件
 if (in_array($auth, $name) && $intersect == $param) { //如果节点相符且url参数满足
  $list[] = $auth;
 }
 } else if (in_array($auth, $name)) {
 $list[] = $auth;
 }
}

in_array($auth, $name) 如果 权限列表中 其中一条权限 等于 当前需要校验的权限 则加入到$list中
注:

$list = array(); //保存验证通过的规则名

if ($relation == 'or' and !empty($list)) {
 return true;
}

$diff = array_diff($name, $list);
if ($relation == 'and' and empty($diff)) {
 return true;
}

$relation == 'or' and !empty($list); //当or时 只要有一条是通过的 则 权限为真
$relation == 'and' and empty($diff); //当and时 $name与$list完全相等时 权限为真

3、获取权限列表:

$authList = $this->getAuthList($uid, $type); //获取用户需要验证的所有有效规则列表

这个主要流程:

获取用户组

$groups = $this->getGroups($uid);
//SELECT `rules` FROM think_auth_group_access a INNER JOIN think_auth_group g on a.group_id=g.id WHERE ( a.uid='1' and g.status='1' )

简化操作就是:

SELECT `rules` FROM think_auth_group WHERE STATUS = '1' AND id='1'//按正常流程 去think_auth_group_access表中内联有点多余....!

取得用户组rules规则字段 这个字段中保存的是think_auth_rule规则表的id用,分割

$ids就是$groups变量最终转换成的 id数组:

$map = array(
 'id' => array('in', $ids),
 'type' => $type,
 'status' => 1,
);

取得think_auth_rule表中的规则信息,之后循环:

foreach ($rules as $rule) {
  if (!empty($rule['condition'])) { //根据condition进行验证
  $user = $this->getUserInfo($uid); //获取用户信息,一维数组
  $command = preg_replace('/\{(\w*?)\}/', '$user[\'\\1\']', $rule['condition']);
  //dump($command);//debug
  @(eval('$condition=(' . $command . ');'));
  if ($condition) {
   $authList[] = strtolower($rule['name']);
  }
  } else {
  //只要存在就记录
  $authList[] = strtolower($rule['name']);
  }
 }
if (!empty($rule['condition'])) { //根据condition进行验证

这里就可以明白getUserInfo 会去获取配置文件AUTH_USER对应表名 去查找用户信息

重点是:

$command = preg_replace('/\{(\w*?)\}/', '$user[\'\\1\']', $rule['condition']);
@(eval('$condition=(' . $command . ');'));

'/\{(\w*?)\}/ 可以看成要匹配的文字为 {字符串} 那么 {字符串} 会替换成$user['字符串']
$command =$user['字符串']

如果

$rule['condition'] = '{age}';
$command =$user['age']
$rule['condition'] = '{age} > 5';
$command =$user['age'] > 10
@(eval('$condition=(' . $command . ');'));

即:

$condition=($user['age'] > 10);

这时再看下面代码 如果为真则加为授权列表

if ($condition) {
  $authList[] = strtolower($rule['name']);
}

希望本文所述对大家基于ThinkPHP框架的PHP程序设计有所帮助。

PHP 相关文章推荐
用在PHP里的JS打印函数
Oct 09 PHP
PHP重定向的3种方式
Mar 07 PHP
深入PHP数据缓存的使用说明
May 10 PHP
PHP函数getenv简介和使用实例
May 12 PHP
PHP安装GeoIP扩展根据IP获取地理位置及计算距离的方法
Jul 01 PHP
PHP数据库表操作的封装类及用法实例详解
Jul 12 PHP
ThinkPHP打水印及设置水印位置的方法
Oct 14 PHP
php并发加锁示例
Oct 17 PHP
PHP实现Google plus的好友拖拽分组效果
Oct 21 PHP
php实现文件上传及头像预览功能
Jan 15 PHP
总结PHP内存释放以及垃圾回收
Mar 29 PHP
php TP5框架生成二维码链接
Apr 01 PHP
ThinkPHP行为扩展Behavior应用实例详解
Jul 22 #PHP
qq登录,新浪微博登录接口申请过程中遇到的问题
Jul 22 #PHP
php.ini save_handler 修改不生效的解决办法
Jul 22 #PHP
PHP中模拟处理HTTP PUT请求的例子
Jul 22 #PHP
ThinkPHP之用户注册登录留言完整实例
Jul 22 #PHP
合并ThinkPHP配置文件以消除代码冗余的实现方法
Jul 22 #PHP
PHP中使用glob函数实现一句话删除某个目录下的所有文件
Jul 22 #PHP
You might like
PHP中读取照片exif信息的方法
2014/08/20 PHP
PHP+jQuery实现滚屏无刷新动态加载数据功能详解
2017/05/04 PHP
PHP使用CURL实现下载文件功能示例
2019/06/03 PHP
20款效果非常棒的 jQuery 插件小结分享
2011/11/18 Javascript
jquery实现弹出窗口效果的实例代码
2013/11/28 Javascript
13 款最热门的 jQuery 图像 360 度旋转插件推荐
2014/12/09 Javascript
jquery实现两个图片渐变切换效果的方法
2015/06/25 Javascript
浅析js中substring和substr的方法
2015/11/09 Javascript
Nodejs读取文件时相对路径的正确写法(使用fs模块)
2017/04/27 NodeJs
Node.Js生成比特币地址代码解析
2018/04/21 Javascript
浅谈Angular HttpClient简单入门
2018/05/04 Javascript
JS实现字符串中去除指定子字符串方法分析
2018/05/17 Javascript
Node.js Koa2使用JWT进行鉴权的方法示例
2018/08/17 Javascript
微信小程序实现星级评价效果
2018/12/28 Javascript
el-select数据过多懒加载的解决(loadmore)
2019/05/29 Javascript
微信小程序自定义扫码功能界面的实现代码
2020/07/02 Javascript
基于VSCode调试网页JavaScript代码过程详解
2020/07/20 Javascript
vue使用过滤器格式化日期
2021/01/20 Vue.js
Python爬虫模拟登录带验证码网站
2016/01/22 Python
pandas.DataFrame 根据条件新建列并赋值的方法
2018/04/08 Python
Python直接赋值、浅拷贝与深度拷贝实例分析
2019/06/18 Python
flask/django 动态查询表结构相同表名不同数据的Model实现方法
2019/08/29 Python
python3 字符串知识点学习笔记
2020/02/08 Python
Python如何使用神经网络进行简单文本分类
2021/02/25 Python
分享一个H5原生form表单的checkbox特效代码
2018/02/26 HTML / CSS
W3C公布最新的HTML5标准草案
2008/10/17 HTML / CSS
Tommy Hilfiger美国官网:美国高端休闲领导品牌
2019/01/14 全球购物
Carolina工作鞋官网:Carolina Footwear
2019/03/14 全球购物
巴西Mr. Cat在线商店:购买包包和鞋子
2019/09/08 全球购物
求职自荐书范文
2013/12/04 职场文书
物业管理专业求职信
2014/06/11 职场文书
运动会演讲稿300字
2014/08/25 职场文书
教师纪念9.18事件演讲稿范文
2014/09/14 职场文书
婚育证明样本
2015/06/16 职场文书
Python图片检索之以图搜图
2021/05/31 Python
Oracle 触发器trigger使用案例
2022/02/24 Oracle