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 相关文章推荐
无限级别菜单的实现
Oct 09 PHP
Discuz! 5.0.0论坛程序中加入一段js代码,让会员点击下载附件前自动弹出提示窗口
Apr 18 PHP
Gregarius中文日期格式问题解决办法
Apr 22 PHP
php array_intersect()函数使用代码
Jan 14 PHP
PHP 简易输出CSV表格文件的方法详解
Jun 20 PHP
smarty表格换行实例
Dec 15 PHP
php自动给网址加上链接的方法
Jun 02 PHP
thinkPHP下ueditor的使用方法详解
Dec 26 PHP
详解PHP中websocket的使用方法
Sep 15 PHP
利用PHP抓取百度阅读的方法示例
Dec 18 PHP
ThinkPHP 3使用OSS的方法
Jul 19 PHP
Laravel 已登陆用户再次查看登陆页面的自动跳转设置方法
Sep 30 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
Adodb的十个实例(清晰版)
2006/12/31 PHP
不支持fsockopen但支持culr环境下下ucenter与modoer通讯问题
2011/08/12 PHP
PHP设计模式 注册表模式
2012/02/05 PHP
php校验公钥是否可用的实例方法
2019/09/17 PHP
基于thinkphp6.0的success、error实现方法
2019/11/05 PHP
通过JAVASCRIPT读取ASP设定的COOKIE
2006/11/24 Javascript
Extjs学习笔记之三 extjs form更多的表单项
2010/01/07 Javascript
parseInt parseFloat js字符串转换数字
2010/08/01 Javascript
简介JavaScript中的setTime()方法的使用
2015/06/11 Javascript
JavaScript获取function所有参数名的方法
2015/10/30 Javascript
jquery实现移动端点击图片查看大图特效
2020/09/11 Javascript
JavaScript jquery及AJAX小结
2016/01/24 Javascript
JS实现搜索框文字可删除功能
2016/12/28 Javascript
超全面的javascript中变量命名规则
2017/02/09 Javascript
Ionic2开发环境搭建教程
2020/08/20 Javascript
基于Vue.js 2.0实现百度搜索框效果
2020/12/28 Javascript
原生js封装运动框架的示例讲解
2017/10/01 Javascript
elementUI vue this.$confirm 和el-dialog 弹出框 移动 示例demo
2019/07/03 Javascript
js中的面向对象之对象常见创建方法详解
2019/12/16 Javascript
python备份文件的脚本
2008/08/11 Python
python解析xml文件实例分享
2013/12/04 Python
使用graphics.py实现2048小游戏
2015/03/10 Python
Python遍历指定文件及文件夹的方法
2015/05/09 Python
python开发之for循环操作实例详解
2015/11/12 Python
Python获取文件所在目录和文件名的方法
2017/01/12 Python
Python使用正则表达式抓取网页图片的方法示例
2017/04/21 Python
如何利用python查找电脑文件
2018/04/27 Python
Python实现手写一个类似django的web框架示例
2018/07/20 Python
python字典一键多值实例代码分享
2019/06/14 Python
Python实现图片添加文字
2019/11/26 Python
廉价航班、机票和酒店:JustFly
2018/02/07 全球购物
社区六一儿童节活动总结
2015/02/11 职场文书
2016教师读书思廉心得体会
2016/01/23 职场文书
如何拟写通知正文?
2019/04/02 职场文书
HTML中table表格拆分合并(colspan、rowspan)
2021/04/07 HTML / CSS
聊聊golang中多个defer的执行顺序
2021/05/08 Golang