什么是RBAC?
基于角色的权限访问控制
(Role-Based Access Control)
比较好的解释:
http://blog.csdn.net/painsonline/article/details/7183613/
数据库结构分析:
4张表玩转权限控制
用户表
qing_admin
CREATE TABLE `qing_admin` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(32) NOT NULL DEFAULT '',
`password` varchar(32) NOT NULL,
`create_time` int(11) NOT NULL DEFAULT '0',
`mobile` varchar(100) NOT NULL DEFAULT '0' COMMENT 'æƒé™å—符串',
`last_login_time` int(11) NOT NULL DEFAULT '0',
`status` tinyint(1) DEFAULT '1',
`email` varchar(20) NOT NULL,
`group_id` varchar(10) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;
用户组表(角色表)一个用户可以拥有多个角色,角色表
qing_auth_group
CREATE TABLE `qing_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=7 DEFAULT CHARSET=utf8;
用户与角色关联表
qing_auth_group_access
CREATE TABLE `qing_auth_group_access` (
`uid` mediumint(8) unsigned NOT NULL,
`group_id` mediumint(8) unsigned NOT NULL,
UNIQUE KEY `uid_group_id` (`uid`,`group_id`),
KEY `uid` (`uid`),
KEY `group_id` (`group_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
权限表(更像是菜单表,无限分类技术)
qing_auth_rule
CREATE TABLE `qing_auth_rule` (
`id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
`name` char(80) NOT NULL DEFAULT '' COMMENT '应用/控制器(大驼峰)/方法',
`title` char(20) NOT NULL DEFAULT '' COMMENT '操作标题',
`type` tinyint(1) NOT NULL DEFAULT '1' COMMENT '放入的版块位置',
`status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用(0禁用 1启用),默认启用',
`condition` char(100) NOT NULL DEFAULT '',
`parent_id` mediumint(9) NOT NULL DEFAULT '0' COMMENT '上级规则id 0表示顶级规则',
`show` tinyint(1) NOT NULL DEFAULT '1' COMMENT '菜单是否显示(0不显示 1显示),默认显示',
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) ENGINE=MyISAM AUTO_INCREMENT=24 DEFAULT CHARSET=utf8;
权限控制的根本:
落实到代码上实际是http请求到对应页面(controller对应的action)的时候进行的拦截判断处理。用户登录时判断用户包含了哪些角色,该角色对应了哪些权限(也就是哪些控制器下的方法)并将其展示出来
thinkphp6.0.*框架实现源码
中间件做login登录拦截
前置中间-使用中间件做后台登录拦截,如果登录合法,就放行
1、判断当前有没有登录session
2、判断当前是不是登录页面,多次重定向的问题
3、后置中间件,弊端-在没有登录的状态下,就已经执行了代码,执行之后再跳转到登录
<?php
declare (strict_types = 1);
namespace app\dongadmin\middleware;
class Check
{
/**
* 处理请求
*
* @param \think\Request $request
* @param \Closure $next
* @return Response
*/
public function handle($request, \Closure $next)
{
//1、判断当前有没有登录session
//2、判断当前是不是登录页面,多次重定向的问题
//后置中间件,弊端-在没有登录的状态下,就已经执行了代码,执行之后再跳转到登录
//未登录场景跳转到登录页
if(empty(session('adminAccount')) && !preg_match('/login/',$request->pathinfo())){
return redirect((string) url('login/index'));
}
//使用中间件做后台登录拦截,如果登录合法,就放行
return $next($request);
}
}
1、登录时判断是session是否存在adminAccount用户信息,存在则直接跳转到后台首页
2、提价登录信息逻辑post校验
<?php
namespace app\dongadmin\controller;
use think\facade\Db;
use app\BaseController;
class Login extends BaseController
{
//后台登录的逻辑
public function index()
{
//已登录直接跳转
$account = session('adminAccount');
if($account && $account['id']) {
return redirect(url('index/index'));
}
if(request()->isPost()) {
$data = input('post.');
// 通过用户名 获取 用户相关信息
$adminData = Db::name('admin')->where('username',$data['username'])->find();//一维数组
if(!$adminData || $adminData['status'] !=1 ) {
return alert('用户不存在,或者此用户未被审核通过','/dongadmin/login',5,3);
}
if(!captcha_check($data['verifycode'])) {
// 校验失败
return alert('验证码不正确','/dongadmin/login',5,3);
}
if($adminData['password'] !=password_salt($data['password'])) {
return alert('密码不正确','/dongadmin/login',5,3);
}
//存入缓存
session('adminAccount', $adminData);
return alert('登录成功!','/dongadmin/index/index',6,3);
}else {
return view();
}
}
}
thinkphp5.1框架实现源码
前端用户登录,post提交用户表单信息
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>登录页面</title>
</head>
<body>
<form action="{:url('Login/check')}" method="post">
<p>用户名:</p>
<p><input type="text" name="username"></p>
<p><input type="submit" value="OK"></p>
</form>
</body>
</html>
check方法检查提交用户是否存在,获取查询数据库的用户信息将其保存在SESSION中跨页面访问用户信息并跳转到首页
<?php
namespace app\index\controller;
use think\Controller;
use think\facade\Session;
use think\facade\Request;
use think\Db;
class Login extends Controller
{
public function index()
{
return $this->fetch();
}
public function check()
{
$username = Request::post('username');
$data = Db::table('qing_admin')->where('username',$username)->find();
//假设已经登录成功
if($data){
Session::set('username',$data['username']);
Session::set('user_id',$data['id']);
$this->success('登录成功','Index/index');
}
//清除session
Session::clear();
$this->error('该用户不存在,请重新输入');
}
}
用于展示登录用户具有访问那些控制器下目录权限,需要注意的是在访问前都会去调用父类(RbacControll)里的初始化方法(initialize)查询该用户的权限具有访问哪些目录
<?php
namespace app\index\controller;
class Index extends RbacControll
{
public function index()
{
echo "首页,用于展示登录用户具有访问那些控制器下目录权限";
}
public function select()
{
echo "查询";
}
public function delete()
{
echo "删除";
}
public function update()
{
echo "修改";
}
public function add()
{
echo "增加";
}
}
这是每个后端控制器需要继承的父类,用于用户访问控制器下的方法前检查用户是否具有访问权限
<?php
namespace app\index\controller;
use think\Controller;
use think\facade\Session;
use think\facade\Request;
use think\Db;
//用于把控权限操作
class RbacControll extends Controller
{
//用于继承该类的每个方法执行前都会先执行该初始化方法
public function initialize()
{
//获取访问的控制器(Controll)与方法名(Action)用于判断访问的用户是否具有访问该控制器(Controll)下方法(Action)的权限
$controller = Request::instance()->controller();
$action = Request::instance()->module();
$user_id = Session::get('user_id');
$rules = Db::table('qing_auth_group')->leftJoin ('qing_auth_group_access','qing_auth_group.id = qing_auth_group_access.group_id')->where('qing_auth_group_access.uid',$user_id)->field('rules')->find();
$data = Db::table('qing_auth_rule')->where('id','IN',$rules['rules'])->select();
//查询用户具有访问的目录 子查询,使用到闭包传参
/* $data = Db::table('qing_auth_rule')->where('id','IN', function ($query) use ($user_id){$query->table('qing_auth_group')->leftJoin ('qing_auth_group_access','qing_auth_group.id = qing_auth_group_access.group_id')->where('qing_auth_group_access.uid',$user_id)->field('rules');})->select()->getLastSql();*/
if (!$data) {
$this->error('无权操作', 'login/index');
}
dump($data);
}
}