个人博客系统角色-权限管理
概述
这里是我对于个人博客系统的进一步优化,解决了管理员用户登录的权限管理强烈建议将代码下载下来对照代码阅读
github地址:链接:wyqblog
https://github.com/wyqgg/wyqblog.git
RBAC:管理员通过角色访问权限,管理员拥有角色,角色拥有权限,这样可以使得不同的角色进入系统能够使用的权限。
1、表的设计
在这里我们可以选择两种模式完成角色-权限管理、一种是3表模式、一种是5表模式、这里我简单说明一下这两种结构的区别,这是我自己对于3表结构和5表结构的理解
由该图可以知道三表模式和五表模式之间最主要的区别就是,在处理两个表之间关系的区别,五表模式都是多对多关联新建一个表来记录两个表之间的主键,五表模式是最规范的。而三表模式处理两个表之间的关系时 管理员和角色是一对多关联,将角色表的主键放在管理员表中,角色表和权限表因为是多对多联系所以这里是将权限表中的多个主键以逗号隔开的形式存在角色表中,这样就可以使用三表模式实现权限管理了。我这里使用的是三表结构
数据表的代码
1.1、admin表
//建表sql
CREATE TABLE `admins` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`role_id` int(11) DEFAULT NULL COMMENT '角色id',
`username` varchar(255) NOT NULL COMMENT '用户名',
`password` varchar(255) NOT NULL DEFAULT '123456' COMMENT '密码',
`phone` varchar(255) NOT NULL COMMENT '手机号码',
`nickname` varchar(255) DEFAULT NULL COMMENT '昵称',
`sign` varchar(255) DEFAULT NULL COMMENT '用户签名',
`email` varchar(255) NOT NULL COMMENT '邮箱',
`image` varchar(255) DEFAULT NULL COMMENT '管理员头像',
PRIMARY KEY (`id`),
UNIQUE KEY `phone` (`phone`) USING BTREE COMMENT '手机号唯一',
UNIQUE KEY `email` (`email`) USING BTREE COMMENT '邮箱唯一'
) ENGINE=InnoDB AUTO_INCREMENT=45 DEFAULT CHARSET=gbk;
//测试数据这里我的数据库名为wyq,这里需要换成自己的数据库名
INSERT INTO `wyq`.`admins`(`id`, `role_id`, `username`, `password`, `phone`, `nickname`, `sign`, `email`, `image`) VALUES (43, 1, 'admin', '408d2b2b5cc43be7a34822c28a2ec3a7', '13451170193', NULL, NULL, '1835660803@qq.com', NULL);
INSERT INTO `wyq`.`admins`(`id`, `role_id`, `username`, `password`, `phone`, `nickname`, `sign`, `email`, `image`) VALUES (44, 2, 'wyq', '408d2b2b5cc43be7a34822c28a2ec3a7', '13451170191', NULL, NULL, '1835660809@qq.com', NULL);
1.2、role表
//建表sql
CREATE TABLE `role` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '角色id',
`auth_ids` varchar(255) DEFAULT NULL COMMENT '权限集合',
`role_name` varchar(255) DEFAULT NULL COMMENT '角色名',
`auth_desc` varchar(255) DEFAULT NULL COMMENT '角色详细介绍',
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=gbk;
//测试数据
INSERT INTO `wyq`.`role`(`id`, `auth_ids`, `role_name`, `auth_desc`) VALUES (1, '1,2,3,4,5,6,7', '超级管理员', '全部权限');
INSERT INTO `wyq`.`role`(`id`, `auth_ids`, `role_name`, `auth_desc`) VALUES (2, '1,2,3', '运营', '运营只有部分权限');
1.3、auth表
CREATE TABLE `auth` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '权限id',
`auth_name` varchar(255) DEFAULT NULL COMMENT '权限名',
`is_menu` int(20) DEFAULT NULL COMMENT '是否是菜单项',
`pid` int(11) DEFAULT NULL COMMENT '父级菜单',
`path` varchar(255) DEFAULT NULL COMMENT '控制器方法地址',
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=7 DEFAULT CHARSET=gbk;
//测试数据
INSERT INTO `wyq`.`auth`(`id`, `auth_name`, `is_menu`, `pid`, `path`) VALUES (1, '管理员管理', 1, 0, '');
INSERT INTO `wyq`.`auth`(`id`, `auth_name`, `is_menu`, `pid`, `path`) VALUES (2, '文章管理', 1, 0, '');
INSERT INTO `wyq`.`auth`(`id`, `auth_name`, `is_menu`, `pid`, `path`) VALUES (3, '用户管理', 1, 0, '');
INSERT INTO `wyq`.`auth`(`id`, `auth_name`, `is_menu`, `pid`, `path`) VALUES (4, '权限操作', 1, 0, '');
INSERT INTO `wyq`.`auth`(`id`, `auth_name`, `is_menu`, `pid`, `path`) VALUES (5, '角色管理', 1, 4, '/role/index');
INSERT INTO `wyq`.`auth`(`id`, `auth_name`, `is_menu`, `pid`, `path`) VALUES (6, '权限管理', 1, 4, '/auth/index');
INSERT INTO `wyq`.`auth`(`id`, `auth_name`, `is_menu`, `pid`, `path`) VALUES (7, '用户管理', 1, 3, '/user/index');
2、代码实现
个人见解:
在这里要实现权限管理,我们需要思考怎么样才能算权限管理,我们需要知道那些页面需要有权限才能操作,那些页面不需要权限就可以进行操作,我在这里设计的是只有登录和注册时不需要权限检测,登录成功的页面都需要进行权限检测,明白了这一点之后,我们还要了解一点既然是很多页面都需要实现权限检测这一功能,这里我们就可以想到面向对象的特性:继承,我们可以先创建一个基础控制器类,然后让之后的所有控制器类都继承这个类,然后我们将权限检测的代码放到基础控制器类的构造函数之中,那么在每次访问控制器时,系统都会对权限进行检测了。因为公司业务使用的是Cakephp,所以我下面用的是Cakephp实现权限检测,思路都是一样的,但是因为框架的特性还是会有差别
2.1 登录控制器 LoginController.php
这里我按步骤编写代码,这个登录模块我之前放在admin模块,但是现在需要做权限检测就不能放在那个模块了,这里我做的权限检测是直接用的控制器名,若后续会出现问题我会继续完善,我们需要在登录成功时将用户的信息存储在session中,这里cakephp我刚学习所以在使用cakephp的session时遇到了很多麻烦具体阅读session解决方案, 这里存储用户信息是为了后面的权限检测
<?php
/**
* Created by PhpStorm.
* User: wyq
* Date: 2021/7/8
* Time: 22:13
*/
//引入session类
App::uses('CakeSession', 'Model/Datasource');
class LoginController extends AppController
{
public $helpers =array('Html', 'Form');
/*
* 渲染用户登录界面
*/
public function login()
{
$this->layout = false;
}
/*
*用户登录逻辑
*/
public function doLogin()
{
//接受参数
$post = $_POST;
//模型中获取用户信息
$data = $this->Login->find_admin($post['username']);
if ($data) {
if ($this->encrypt($post['password']) == $data['Login']['password']) {
//将登录用户信息存在session中
CakeSession::write('admin_info', $data['Login']);
$res = array('code' => 200, 'msg' => 'login success!');
exit(json_encode($res));
} else {
$res = array('code' => 400, 'msg' => 'password error!');
exit(json_encode($res));
}
} else {
$res = array('code' => 400, 'msg' => 'no user!');
exit(json_encode($res));
}
}
/*
* 渲染注册页面
*/
public function regist()
{
$this->layout = false;
}
/*
* 用户注册逻辑
*/
public function doRegist()
{
$post = $_POST;
//查找手机号码是否存在
$countPhone = $this->Admin->find('count', array('conditions' => array('Admin.phone' => $post['phone'])));
if ($countPhone) {
$msg = array('code' => 404, 'data' => '手机号码已注册!');
exit(json_encode($msg));
}
$countEmail = $this->Admin->find('count', array('conditions' => array('Admin.email' => $post['email'])));
if ($countEmail) {
$msg = array('code' => 404, 'data' => '邮箱已注册!');
exit(json_encode($msg));
}
$post['password'] = $this->encrypt($post['password']);
$this->Admin->set($post);
if ($this->Admin->validates()) {
$data = $this->Admin->save($post);
if ($data) {
$msg = array('code' => 200, 'data' => $data);
} else {
$msg = array('code' => 404, 'data' => '注册失败');
}
} else {
$errors = $this->Admin->validationErrors;
$msg = array('code' => 404, 'data' => '注册失败,参数验证失败');
}
exit(json_encode($msg));
}
public function encrypt($data)
{
$salt = "123123asdasdasd";
$psw = md5($salt . md5($data));
return $psw;
}
}
2.1 基础控制器 Appcontroller.php
这里我编写代码时处理了很久 ,因为cakephp的特性与我之前接触的PHP框架不同,Cakephp所有控制器都是要必须继承AppController控制器,所以这里不能重新创建一个基础控制器,而是在AppContraller控制器中编写权限检测功能,还有不同的是Cakephp框架定义的有一个方法会在调用一个controller时先调用这个方法,这样就可以将权限检测编写在这个方法中,在我之前接触的Thinkphp 中是编写在构造函数中的,只要写在调用类时首先执行的方法中就行
<?php
App::uses('CakeSession', 'Model/Datasource');
App::uses('Controller', 'Controller');
class AppController extends Controller
{
public $uses = array ('Admin','Role');
//beforeFilter就是Cakephp中调用controller之前执行的方法
public function beforeFilter()
{
//实现父类的方法
parent::beforeFilter();
//获取当前访问的地址 如:/login/login : 截取之后得到:$url = [0=>'',1=>'login',2=>'login']
$url = explode('/',trim($_SERVER['REQUEST_URI']));
//获取控制器名。这里我是将不需要权限检测的功能放在一个控制器里。
$module = $url[1];
//login控制器不需要进行权限检测,这里我只设置了一个,后续还会增加
$NoAuth = array('login');
if (!in_array($module,$NoAuth)){
//获取session中的用户信息
$admin_info = CakeSession::read('admin_info');
if (!$admin_info) {
//没有用户信息直接跳转到登录页面
$this->redirect('login/login');
}
//这里就是获取该用户能看到的菜单
$this->getMenu();
}
}
/*
* 获取当前用户权限菜单
*/
public function getMenu()
{
//从session中获取当前登录用户的角色
$admin_info = CakeSession::read('admin_info');
$role_id = $admin_info['role_id'];
//模型方法获取当前用户的权限
$auth = $this->Role->find_auth($role_id);
//模型方法获取用户的全部菜单
$menu = $this->Role->get_menu($auth);
//这里因为菜单有顶级菜单和次级菜单,故这里可以做处理,这样可以返回父子级树状结构
$menu1 = $this->get_tree_list($menu);
//参数绑定到页面
$this->set('menu', $menu1);
}
//获取父子级树状结构
public function get_tree_list($list){
$temp = array();
foreach ($list as $v) {
$v['son'] = array();
$temp[$v['id']] = $v;
}
foreach ($temp as $k => $v) {
$temp[$v['pid']]['son'][] = &$temp[$v['id']];
}
return isset($temp[0]['son']) ? $temp[0]['son'] : array();
}
}
2.2 框架默认布局代码default.ctp
我们就是在这个页面实现整个后台系统的菜单导航的,所以我们需要在这个页面实现菜单展示,之前页面都是静态数据,现在实现菜单选项,这里我只展示我修改的代码,全部代码可以在我的github上下载文章最上方有链接地址
<div class="layui-side layui-bg-black">
<div class="layui-side-scroll">
<!-- 左侧导航区域(可配合layui已有的垂直导航) -->
<ul class="layui-nav layui-nav-tree" lay-filter="test">
<!--遍历所有菜单权限-->
<?php foreach($menu as $v): ?>
<li class="layui-nav-item layui-nav-itemed">
<!--输出菜单名-->
<a class="" href="javascript:;"><?= $v['auth_name'] ?></a>
<dl class="layui-nav-child">
<!--遍历菜单的子菜单-->
<?php foreach($v['son'] as $v1): ?>
<!--输出子菜单名 链接地址为存储的控制器方法名-->
<dd><a href="<?= $v1['path'] ?>"><?= $v1['auth_name'] ?></a></dd>
<?php endforeach; ?>
</dl>
</li>
<?php endforeach; ?>
</ul>
</div>
</div>
基本页面展示
admin用户页面
wyq用户页面
从这个页面我们可以知道虽然菜单显示没问题,但是wyq用户是没有查看用户信息这个权限的,但是却进入了默认的页面,这里是因为我还没有编写权限检测功能,还存在越权操作
这样基本就实现了菜单的展示,下面就是对权限检测进行设计,这样虽然不一样的管理员进入界面不一样但是如果登录用户知道其他功能的地址,在地址栏中输入地址还是可以进行越权操作,所以我们还需要进行权限检测,以防止越权操作
2.3AppController权限管理代码
这里和获取菜单逻辑基本相同,只是在判断时少了一个是不是菜单条件,模型代码有略微不同,这里我没有编写错误跳转页面,所以越权操作会报错。
public function beforeFilter()
{
parent::beforeFilter();
//获取当前访问的地址 如:/login/login : 截取之后得到:$url = [0=>'',1=>'login',2=>'login']
//这里是访问地址
$url1 = trim($_SERVER['REQUEST_URI']);
$url = explode('/',trim($_SERVER['REQUEST_URI']));
//获取控制器名。这里我是将不需要权限检测的功能放在一个控制器里。
$module = $url[1];
//login控制器不需要进行权限检测
$NoAuth = array('login');
if (!in_array($module,$NoAuth)){
$admin_info = CakeSession::read('admin_info');
if (!$admin_info) {
$this->redirect('login/login');
}
//获取用户菜单
$this->getMenu();
//权限检测
$auth = $this->authCheck();
//鉴权失败
if (!in_array($url1,$auth)){
exit('您没有权限操作!');
}
}
}
/*
* 权限检测防止越权操作
*/
public function authCheck(){
$admin_info = CakeSession::read('admin_info');
$role_id = $admin_info['role_id'];
//获取当前用户的权限
$auth = $this->Role->find_auth($role_id);
//获取权限的信息
$auth_info = $this->Role->get_auth($auth);
return $auth_info;
}
这里还是使用wyq用户登录系统因为登录进入默认的是/user/index页面所以会没有权限进入这样已经基本的实现了权限管理的功能