php au著h权限管理,ThinkPHP5权限认证

有一个网友问,有权限吗?我之前也说了,会加上权限。一直很忙,没有去添加。今天就来更新一下,如何在 thinkphp5 框架中实现权限的控制。

主流的权限控制有 rbac 和 auth 。我先来介绍一下 rbac,然后在此基础之上,再介绍一下 auth 的实现。

rbac 这个系统的设计,就拿我之前开源的系统来讲解吧。正好,那个系统实现的就是 rbac 。你可以到这个地址[github 上的 snake](https://github.com/nick-bai/snake),或者直接到源码下载那一章节下载我演示的代码。

rbac 中文名 叫 角色权限控制,具体的什么解释,你可以自行百度,比我解释的专业。这里,我只是想说明一下,设计 rabc 权限的思路。首先我们的系统必须拥有的表有如下几张:

1、用户表

这个是必须的,因为系统需要用户的登录,这是不可或缺的。

2、节点表

记录着系统中的各个操作节点,方便我们通过这些节点去拼装菜单,以及权限的分配。

> 所谓的节点,在 rbac 中你可以理解成:模块、控制器、方法。这些对应的名字。

3、角色表

存放各种系统的角色。

这几张表有了。讲一下,具体的实现方式。

> 我们 通过 给角色分配一些操作节点的权限,然后再给 用户 指定角色。这样,当用户当用户操作某个 控制器\\方法 的时候,我们检测他所属的角色,是否有这个 节点的规则,就能判断,他是否可以操作这个 方法。

讲到 auth 可以理解成加强版的 rbac,他不仅可以验证节点,同样还可以比 rbac 验证更多的小细节。比如,某个节点,必须要 积分 > 500 的才能操作,因为 rabc 只能控制节点(这个节点就是由 模块\\控制器\\方法名 组成的字串),无法验证别的小细节。而 auth 是验证 规则的,而不是节点。

> 遗憾的是,目前能找到的一些介绍 auth 的 thinkphp 代码,其实就是 rbac,并没有展示 auth 比 rbac 好的地方。另外,其实你做一个 auth 权限系统,也并不一定要官方的那个 auth 类,这个类 thinkphp5 官方暂时未提供。其实你要是理解原理之后,很容易写出和你的系统化完全匹配的 auth 方法来。

在我们做 rbac 的时候,录入的节点是按照 模块、控制器、方法名,这样的顺序录入到 节点表中的,而在我们验证权限的时候,又得将这些 模块、控制器、方法名拼接成字符串。

比如:我们在表中录入 index 、shop、addShop 这三个节点,而我们在 验证的时候,会验证 index/shop/addshop,这样去验证,而我拼装成的这个样子的 字串**index/shop/addshop**就是 auth 中所讲的 规则。其实,我们在 rbac 权限系统中,去分开录入 这样三个 字段,不如直接录入像 auth 这样的规则。反而更利于我们的后续操作。

说到这里,有没有发现,其实 rbac 和 auth 是很像的,这也就是为什么,很多的所谓的讲 auth 的代码,都是“挂羊头卖狗肉”的rbac。其实,我们只要在 节点表 (auth 中称为规则表)中,加入一个 附件条件 字段,在验证规则的同是,去检测 附加条件 是否满足,从而实现更加细节的验证。至于这个附加条件,你怎么去设计。你可以按照官方的那种方式去设计,也可以自己去 设计这个填写格式,反正你自己能有办法解析就行。

我们在正式开始写 auth 系统之前,先来看看,我们需要设计哪些表。

1、用户表

~~~

-- ----------------------------

-- Table structure for auth_user

-- ----------------------------

DROP TABLE IF EXISTS `auth_user`;

CREATE TABLE `auth_user` (

`id` int(11) NOT NULL AUTO_INCREMENT,

`username` varchar(255) COLLATE utf8_bin DEFAULT '' COMMENT '用户名',

`password` varchar(255) COLLATE utf8_bin DEFAULT '' COMMENT '密码',

`loginnum` int(11) DEFAULT '0' COMMENT '登陆次数',

`last_login_ip` varchar(255) COLLATE utf8_bin DEFAULT '' COMMENT '最后登录IP',

`last_login_time` int(11) DEFAULT '0' COMMENT '最后登录时间',

`real_name` varchar(255) COLLATE utf8_bin DEFAULT '' COMMENT '真实姓名',

`status` int(1) DEFAULT '0' COMMENT '状态',

`roleid` int(11) DEFAULT '1' COMMENT '用户角色id',

PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

-- ----------------------------

-- Records of auth_user

-- ----------------------------

INSERT INTO `auth_user` VALUES ('1', 'admin', '21232f297a57a5a743894a0e4a801fc3', '32', '127.0.0.1', '1490852367', 'admin', '1', '1');

INSERT INTO `auth_user` VALUES ('2', 'xiaobai', '4297f44b13955235245b2497399d7a93', '6', '127.0.0.1', '1470368260', '小白', '1', '2');

~~~

2、角色表 (在 auth 中称之为 权限组 其实是一个概念)

~~~

-- ----------------------------

-- Table structure for auth_role

-- ----------------------------

DROP TABLE IF EXISTS `auth_role`;

CREATE TABLE `auth_role` (

`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',

`rolename` varchar(155) NOT NULL COMMENT '角色名称',

`rule` varchar(255) DEFAULT '' COMMENT '权限节点数据',

PRIMARY KEY (`id`)

) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

-- ----------------------------

-- Records of auth_role

-- ----------------------------

INSERT INTO `auth_role` VALUES ('1', '超级管理员', '');

INSERT INTO `auth_role` VALUES ('2', '系统维护员', '1,2,3,4,5,6,7,8,9,10');

INSERT INTO `auth_role` VALUES ('3', '新闻发布员', '1,2,3,4,5');

~~~

3、规则表

~~~

-- ----------------------------

-- Table structure for auth_node

-- ----------------------------

DROP TABLE IF EXISTS `auth_node`;

CREATE TABLE `auth_node` (

`id` int(11) NOT NULL AUTO_INCREMENT,

`node_name` varchar(155) NOT NULL DEFAULT '' COMMENT '节点名称',

`rule` varchar(155) NOT NULL COMMENT '权限规则',

`is_menu` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否是菜单项 1不是 2是',

`typeid` int(11) NOT NULL COMMENT '父级节点id',

`style` varchar(155) DEFAULT '' COMMENT '菜单样式',

`condition` varchar(155) DEFAULT NULL COMMENT '附加条件',

PRIMARY KEY (`id`)

) ENGINE=MyISAM AUTO_INCREMENT=15 DEFAULT CHARSET=utf8;

-- ----------------------------

-- Records of auth_node

-- ----------------------------

INSERT INTO `auth_node` VALUES ('1', '用户管理', '#', '2', '0', 'fa fa-users', null);

INSERT INTO `auth_node` VALUES ('2', '用户列表', 'user/index', '2', '1', '', null);

INSERT INTO `auth_node` VALUES ('3', '添加用户', 'user/useradd', '1', '2', '', null);

INSERT INTO `auth_node` VALUES ('4', '编辑用户', 'user/useredit', '1', '2', '', null);

INSERT INTO `auth_node` VALUES ('5', '删除用户', 'user/userdel', '1', '2', '', null);

INSERT INTO `auth_node` VALUES ('6', '角色列表', 'role/index', '2', '1', '', null);

INSERT INTO `auth_node` VALUES ('7', '添加角色', 'role/roleadd', '1', '6', '', null);

INSERT INTO `auth_node` VALUES ('8', '编辑角色', 'role/roleedit', '1', '6', '', null);

INSERT INTO `auth_node` VALUES ('9', '删除角色', 'role/roledel', '1', '6', '', null);

INSERT INTO `auth_node` VALUES ('10', '分配权限', 'role/giveaccess', '1', '6', '', null);

INSERT INTO `auth_node` VALUES ('11', '系统管理', '#', '2', '0', 'fa fa-desktop', null);

INSERT INTO `auth_node` VALUES ('12', '数据备份/还原', 'data/index', '2', '11', '', null);

INSERT INTO `auth_node` VALUES ('13', '备份数据', 'data/importdata', '1', '12', '', null);

INSERT INTO `auth_node` VALUES ('14', '还原数据', 'data/backdata', '1', '12', '', null);

~~~

> 关于系统的管理员登录、角色的增删改查、规则的增删改查、用户的增删改查等,这些基础的功能,在本部分不做过多的介绍。相信你通过前面的,用户的增删改查,已经会用 thinkphp5 完成 CURD 的操作了。本初只重点介绍,权限系统的具体起作用的部分。

## 从登陆开始讲起

登录的基础功能,校验用户名,密码,验证码这些内容,代码中都有,此处不做过多的介绍。开始看看,我们在登陆的时候,应该做哪些权限的工作。

确认用户一切信息正确之后,我做了如下的操作 Login.php

~~~

//获取该管理员的角色信息

$user = new UserType();

$info = $user->getRoleInfo($hasUser['roleid']);

~~~

根据用户的 角色id 去获取用户所拥有的 权限信息。我在用户表中设置了一个 rule 的字段,这个字段以逗号隔开,存储着用户的权限节点的 id。例如 rule 字段的结果是 1,2 。 那么对应的节点就是 # 和 user/index 也就是拥有,用户列表查看的权限。我们拿着用户的权限 rule 去 node 表中把他拥有的 权限节点数据全部查出。 Usertype.php

~~~

/**

* 获取角色信息

* @param $id

*/

public function getRoleInfo($id){

$result = db('role')->where('id', $id)->find();

if(empty($result['rule'])){

$where = '';

}else{

$where = 'id in('.$result['rule'].')';

}

$res = db('node')->field('rule')->where($where)->select();

foreach($res as $key=>$vo){

if('#' != $vo['rule']){

$result['action'][] = $vo['rule'];

}

}

return $result;

}

~~~

> 我们在此处设计的是 超级管理员的 rule 是空,以此来标识他是超级管理员,超级管理员拥有全部的权限。

查询出全部的节点,把这些节点,存储到 session 中,这样我们就不需要每次都去查取用户的权限节点,提高效率。

~~~

session('username', $username);

session('id', $hasUser['id']);

session('role', $info['rolename']); //角色名

session('action', $info['action']); //角色权限

~~~

action 节点的数据如下:

~~~

Array

(

[0] => user/index

[1] => user/useradd

[2] => user/useredit

[3] => user/userdel

[4] => role/index

[5] => role/roleadd

[6] => role/roleedit

[7] => role/roledel

[8] => role/giveaccess

[9] => data/index

[10] => data/importdata

[11] => data/backdata

)

~~~

用户拥有的节点,就放在这样的数组里面。这样,当用户操作某一个节点时候,直接判断所操作的节点是否在这个数组中即可。这些都是后面的话了,我们接着看,login 之后操作了哪些。

登录成功之后,跳转到 index/index 控制器,而 index 控制器,又继承了 Base.php 这个基类,这个基类中,我们可以做一些全局的检测。

## 权限检测 Base.php

我们来看一下,Base.php 做了哪些操作

~~~

public function _initialize()

{

if(empty(session('username'))){

$this->redirect(url('login/index'));

}

//检测权限

$canDo = authCheck();

if(!$canDo){

$this->error('没有权限');

}

//获取权限菜单

$node = new Node();

$this->assign([

'username' => session('username'),

'menu' => $node->getMenu(session('rule')),

'rolename' => session('role')

]);

}

~~~

用户未登录,跳转到登录。如果用户登录成功了,此时我们进行权限的检测。auCheck(),定义在 common.php 中

~~~

function authCheck(){

$control = lcfirst(request()->controller());

$action = lcfirst(request()->action());

//跳过登录系列的检测以及主页权限

if(!in_array($control, ['login', 'index'])){

if(!in_array($control . '/' . $action, session('action'))){

return false;

}

}

return true;

}

~~~

此处我们只是做了节点的验证,你可以理解成目前还是 rbac 也就是市面上绝大多数的 所谓的 rbac 就只是检测到这一步。很简单,拼接现在的操作节点字串,是否在该用户所在的权限数组中就可以了。不在,提示无权限。

最后,比较主要的步骤,根据用户的权限节点,拼接出用户拥有的 左侧操作菜单。getMenu()

~~~

/**

* 根据节点数据获取对应的菜单

* @param $nodeStr

*/

public function getMenu($nodeStr = '')

{

//超级管理员没有节点数组

$where = empty($nodeStr) ? 'is_menu = 2' : 'is_menu = 2 and id in('.$nodeStr.')';

$result = db('node')->field('id,node_name,typeid,rule,style')

->where($where)->select();

$menu = prepareMenu($result);

return $menu;

}

~~~

这里又调用了 定义在 common.php 中的 prepareMenu() 方法

~~~

/**

* 整理菜单住方法

* @param $param

* @return array

*/

function prepareMenu($param)

{

$parent = []; //父类

$child = []; //子类

foreach($param as $key=>$vo){

if($vo['typeid'] == 0){

$vo['href'] = '#';

$parent[] = $vo;

}else{

$vo['href'] = url($vo['rule']); //跳转地址

$child[] = $vo;

}

}

foreach($parent as $key=>$vo){

foreach($child as $k=>$v){

if($v['typeid'] == $vo['id']){

$parent[$key]['child'][] = $v;

}

}

}

unset($child);

return $parent;

}

~~~

我们只要在页面中,对应的位置,渲染出这个整理更好的菜单,就可以完成操作栏,根据不同的权限,显示不同的菜单了。

至此, rbac 部分算是结束了。

上一章的结尾,我说的是至此,rbac 的部分结束了。有人可能感觉很奇怪,不是说讲的是 auth 吗,怎么又 rbac 了。其实,你可以把 auth 理解成 rbac 的加强版。一个 auth 权限系统,首先要有 rbac 的功能。接下来才是,其区别于 rbac 的重点所在。也是 绝大部分所谓的 auth 权限系统未提及的部分。

> 我在这个文档中讲解的 auth 权限,并没有用到 thinkphp 3.2 中给到的 auth 类,如果你想找通过改 3.2 那个类而来的 auth 权限系统。那你可能要失望了,不是我不会改那个类,而是我觉得,你懂了原理之后,根本没必要拘泥于那个类,完全可以自己定义。

**如何正我们的 rbac 基础之上,改成 auth 呢?**

auth 区别于 rabc 的主要点是,auth 检测的是规则,而规则我们已经有了,那就是 node 表中的 rule 字段,其实就是节点的标识。另外一点, auth 系统中通常会在 节点后面加一个 condition 字段,以此来标识,想要拥有这个权限,你还应该有哪些额外的条件。而这个条件的填写和解析是最为关键的点。

## 开始修改

首先,我们要制定一个额外的条件规则,本处为了解析的方便,以及展示原理的原则,我设计一个简单的条件规则

~~~

user|id={uid} and loginnum > 20

~~~

条件牵扯的表|条件字段=当前用户id and 条件字段 > 20

这个语句的意思就是 某个权限的需要满足这个用户在 user 表中登录的次数大于 20 才能有权限。

从之前的我展示的数据可以看到,管理员的登录次数是 30多次。那我们就以这个例子进行讲解。首先在 添加用户 这个权限字段,也就是 node 表中的第三条 添加一个 condition 字段值 user|id={uid} and loginnum>200,也就是规定,useradd 操作的额外权限是 操作次数必须大于 200 次的才可以。此时我们看看,用户是否有添加用户的权限

![](https://box.kancloud.cn/9ac0e0d8f5c07c51e84e2e3e758162ef_1669x348.jpg)

这种页面中的按钮权限,是传统 rbac 很那去控制的。可见此时,用户有添加 用户的权限。我们修改一下权限检测方法 authCheck

~~~

function authCheck($condition=false, $url=''){

$control = lcfirst(request()->controller());

$action = lcfirst(request()->action());

if(empty($condition)){

$checkUrl = $control . '/' . $action;

}else{

$checkUrl = $url;

if(empty($checkUrl)) return false;

// 检测附加条件

$condition = db('node')->field('condition')->where("rule = '" . $checkUrl . "'")->find();

// 解析附加添加 形如:user|id={uid} and loginnum > 20

if(empty($condition)){

return true;

}

$rule = explode("|", $condition['condition']);

unset($condition);

$table = $rule['0'];

$where = str_replace("{uid}", session('id'), $rule['1']);

$can = db($table)->where($where)->find();

if(empty($can)) return false;

}

if(!in_array($control, ['login', 'index'])){

if(!in_array($checkUrl, session('action'))){

return false;

}

}

return true;

}

~~~

这样我们的简单的 权限检测 函数就完成了。当然这个函数还很弱,只能检测某一种规则。如果你想检测复杂的规则,你可以自己完善和定制更多的规则,原理就是你得会解析这些规则。就像这样

~~~

$rule = explode("|", $condition['condition']);

unset($condition);

$table = $rule['0'];

$where = str_replace("{uid}", session('id'), $rule['1']);

~~~

当然,github上 官方已经写好了一个类[https://github.com/yunwuxin/think-auth](https://github.com/yunwuxin/think-auth)后面我会讲解这个的用法,当然这个就非常强大了,支持很多种认证。

## 如何去验证

比如我们去验证这个需要额外权限的 添加用户 按钮是否需要展示,在按钮页面

~~~

{if(authCheck(true, 'user/useradd'))}

添加用户

{/if}

这样就能验证,这个添加按钮的额外权限了。

## 预告

后面我会研究 官方给的那个扩展,讲解一个强大的 auth 权限,本次只是讲解自己去实现 auth 的原理。

>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值