RBAC权限控制
RBAC: Role Based Access Controller ,即基于角色的访问权限控制:###
简单的来说就是不同的用户登录时,所拥有的权限也菜单不同
首先 数据表的设计 一般采用3张表 即 用户表 角色表 权限表
关系如下图:
先在控制器 定义一个方法 基本引入模板 定义验证器 判断权限是否为顶级权限 顶级权限是不会对控制器名和方法名判断
在将页面上输入的权限进行入库操作
可在模型中定义无限极分类取出所有的权限
public function getAuthsSon(){
$data = $this->select();
return $this->_getAuthsSon($data);
}
private function _getAuthsSon($data,$pid = 0, $deep = 1){
static $result = [];
foreach($data as $auth){
if($auth['pid'] == $pid){
$auth['deep'] = $deep;
$result[$auth['auth_id']] = $auth;
$this->_getAuthsSon($data,$auth['auth_id'],$deep+1);
}
}
return $result;
}
模板中 当选择顶级权限是 控制器和方法名不可选 ,否则可用,因为顶级权限没有控制器名和方法名
$("select[name='pid']").change(function(){
var pid = $(this).val();
if(pid == 0){
$("input[name='auth_c'],input[name='auth_a']").prop('readonly',true).val();
}else{
$("input[name='auth_c'],input[name='auth_a']").prop('readonly',false).val();
}
});
$("select[name='pid']").change();
注: 需要引入jquery..
这是是将表单的属性设置为readonly使其不可用
表单的readonly属性和disabled的区别
相同点:都可用是input输入框不可编辑:
不同点:
设置readonly后,PHP通过$_POST 可用接受到name属性值
设置disabled后,PHP 接受不到name属性值
在模板中遍历数据可以使用str_repeat($str,$num)[ 重复某一个字符串 $str重复的字符串,$num重复的次数] 来设置增加缩进
<td>{:str_repeat(' ',$list['deep']*2)}{$list['auth_name']}</td>
<td>{$lists[ $list['pid'] ]['auth_name']?:'' }</td>
<td>{$list['auth_c']}</td>
<td>{$list['auth_a']}</td>
添加角色及分配权限
键一个后台控制器 role 取出所有权限
//取出所有的权限
$authModel = new Auth();
$authData = $authModel->select()->toArray();
/* 循环所有的权限*以auth_id为每个元素的下标 */
$auths = [];
foreach($authData as $auth){
$auths[ $auth['auth_id'] ] = $auth;
}
/* 循环所有的权限* 通过pid进行划分为同一组 */
$children = [];
foreach($authData as $auth){
$children[ $auth['pid'] ][] = $auth['auth_id'];
}
这里取权限有些小技巧
第一:取出所有权限:并且以auth_id为每个元素的下标
第二:取出所有权限,通过pid进行分组,把具有相同的pid划分为同一组
在页面循环通过分组为下标的pdi为下标
<table width="600px" border="1px" rules="all" class="box">
<!-- 循环顶级(1级权限) -->
<?php foreach($children[0] as $one_auth_id): ?>
<tr>
<th><input onclick="all_select(this);" type="checkbox" value='<?php echo $one_auth_id; ?>' name="auth_id_list[]"><?php echo $auths[ $one_auth_id ]['auth_name']; ?></th>
<td>
<!-- 循环顶级(2级权限) -->
<?php foreach($children[ $one_auth_id ]?:array() as $two_auth_id): ?>
<ul class="ul_f">
<b><input onclick="all_select(this);up_select(this,'{$one_auth_id}')" value="<?php echo $two_auth_id; ?>" type="checkbox" name="auth_id_list[]"><?php echo $auths[ $two_auth_id ]['auth_name']; ?></b>
<ul>
<!-- 循环顶级(3级权限) -->
<?php foreach($children[ $two_auth_id ]?:array() as $three_auth_id): ?>
<li class="son">
<input value="<?php echo $three_auth_id; ?>" type="checkbox" onclick="up_select(this,'{$two_auth_id},{$one_auth_id}')" name="auth_id_list[]"><?php
echo $auths[$three_auth_id]['auth_name'] ?>
</li>
<?php endforeach ;?>
</ul>
</ul>
<?php endforeach; ?>
</td>
</tr>
<?php endforeach; ?>
</table>
角色添加特效 当子元素被选中是 父元素默认被选中 , 当父元素被选中时,所有子元素默认被选中
function all_select(obj){
//找到当前对象的父元素的下一个兄弟元素,并且找到名为input的子孙元素
//都跟着当前元素的选中状态,都选中那么都不选中
$(obj).parent().next().find('input').prop('checked',obj.checked);
}
function up_select(obj,ids){
console.log(ids);
var arr = ids.split(','); // [1,5]
for(var k in arr){
//把对应的复选框选中
$('input[value='+arr[k]+']').prop('checked',true);
}
//找到当前元素的名为td的祖先,在往下找选中的复选框的个数
var length = $(obj).parents("td").find("input[type='checkbox']:checked").length;
if(length == 0){
$(obj).parents('tr').find('th > input').prop('checked',false);
}
}
权限入库 因为一个角色可能有多个权限,复选框以数组方式入库 但是mysql不支持数组,所以我们先在模型中定义一个入库前的钩子事件
protected static function init(){
Role::event('before_insert',function($role){
$role['auth_id_list'] = implode(',',$role['auth_id_list']);
});
}
<u>implode</u> — 将一个一维数组的值转化为字符串($str,$arr) ;
实现角色列表展示
要将每个角色所拥有的权限都查出来 这里推荐使用FIND_IN_SET和group_concat 这俩个mysql函数
FIND_IN_SET($str,$strlist); 检查str是否在由<u>逗号</u>分割的目标字符串中strlist存在,如果存在返回所在位置,否则返回0;
SELECT FIND_IN_SET(8,'1,2,3,4,5,4,9,8');// 8
那么如何实现不同用户登录展示不同后台菜单
结构:参考第一张图
a/ 用户登录成功的时候,获取到role_id
b/ 通过role_id 获取角色表的auth_id_list字段
c/ 通过权限表auth_id知道在auth_id_list字段存在即可,查出所拥有的权限
步骤一:
在用户登录成功的模型的checkUser方法中,通过角色id把当前的角色所有的权限写入到session中
//设置session信息
session('username',$userInfo['username']);
//通过role_id把管理员的权限写入到session中
$this->writeAuthToSession($userInfo['role_id']);
步骤二:
定义user模型中定义writeAuthToSession方法把权限写入到session中
public function writeAuthToSession($role_id){
//获取auth_id_list的权限
$row = Role::find($role_id);
$auth_id_list = $row['auth_id_list'];
//$auth_id_list * 超级管理员
//$auth_id_list 46,47,48 非超级管理员
if($auth_id_list == '*'){
//超级管理员
$oneAuth = Auth::where('pid',0)->select()->toArray();
foreach ($oneAuth as $k=>$auth) {
$oneAuth[$k]['sonsAuth'] = Auth::where('pid',$auth['auth_id'])->select()->toArray();
}
//超级管理员不做权限控制
session('visitorAuth','*');
}else{
$visitorAuth = []; //保存已有的权限 如: ['user/index','user/add']
//非超级管理员 取出已有的权限
$all_auth = Auth::where('auth_id','in',$auth_id_list)->select()->toArray();
$oneAuth = [];
//筛选出pid=0的权限,即一级权限
foreach ($all_auth as $k=>$auth) {
if($auth['pid'] == 0){
$oneAuth[] = $auth;
}
//存储用户可访问的权限
$visitorAuth[] = strtolower($auth['auth_c'].'/'.$auth['auth_a']);
}
//找出顶级下面的子级权限(2级权限)
foreach($oneAuth as $k=>$auth){
//给每个一级权限添加sonsAuth下标
//去$all_auth所有的权限中去找对应的二级权限
foreach($all_auth as $kk=> $s_auth){
if( $s_auth['pid'] == $auth['auth_id']){
$oneAuth[$k]['sonsAuth'][] = $s_auth;
}
}
}
//保存访问的权限到session中
session('visitorAuth',$visitorAuth);
}
//把权限菜单存储到session中去
session('menuAuth',$oneAuth);
}
步骤三:在html取出session中的权限,遍历访问的菜单
<div class="lefttop"><span></span>※ 控制面板 ※</div>
<dl class="leftmenu">
<!-- 循环一级权限 -->
{:session('role_id')}
<?php foreach(session('menuAuth') as $one_menu):?>
<dd>
<div class="title">
<span><img src="{:config('admin_static')}/images/leftico01.png" /></span><?php echo $one_menu['auth_name']; ?>
</div>
<ul class="menuson">
<!-- 循环一级中的二级权限 -->
<?php foreach($one_menu['sonsAuth'] as $two_menu):?>
<li>
<cite></cite><a href="<?php echo '/index.php/admin/'.$two_menu['auth_c'].'/'.$two_menu['auth_a']; ?>" target="rightFrame"><?php echo $two_menu['auth_name']; ?></a><i></i>
</li>
<?php endforeach;?>
</ul>
</dd>
<?php endforeach;?>
</dl>
但是光在session判断是不行的 用户可以通过url来实现权限翻墙
那么如何实现权限防翻墙呢
解决方法:在基础控制器中定义一个_initialize方法,做防翻墙的操作
要先获取到用户所访问的控制器和方法名,在去做相关判断是否拥有对应的权限
public function _initialize(){
//判断是否登录
if(!session('username')){
$this->error('请先登录',url('admin/public/login'));
}
//strtolower()将字符串转为小写
$now_ca = strtolower( request()->controller().'/'.request()->action() );
$visitorAuth = session('visitorAuth');
//*代表超级管理员
if($visitorAuth == '*' || strtolower( request()->controller() ) == 'index' ){
return ;
}else{
//判断当前控制器和方法名是否属于当前登录用户登录时保存的session中
if(!in_array($now_ca,$visitorAuth)){
//判断是否为ajax请求
if(request()->isAjax()){
//返回错误 终止代码执行
echo json_encode(['message'=>'无权限访问,请联系管理员']);die;
}
//返回信息,终止代码执行
exit('访问错误');
}
}
}
用户登录时判断管理员身份,并保存权限信息在session中
public function writeAuthToSession($role_id){
//获取auth_id_list的权限
$row = Role::find($role_id);
$auth_id_list = $row['auth_id_list'];
//$auth_id_list * 超级管理员
//$auth_id_list 46,47,48 非超级管理员
if($auth_id_list == '*'){
//超级管理员
$oneAuth = Auth::where('pid',0)->select()->toArray();
foreach ($oneAuth as $k=>$auth) {
$oneAuth[$k]['sonsAuth'] = Auth::where('pid',$auth['auth_id'])->select()->toArray();
}
//超级管理员不做权限控制
session('visitorAuth','*');
}else{
$visitorAuth = []; //保存已有的权限 如: ['user/index','user/add']
//非超级管理员 取出已有的权限
$all_auth = Auth::where('auth_id','in',$auth_id_list)->select()->toArray();
$oneAuth = [];
//筛选出pid=0的权限,即一级权限
foreach ($all_auth as $k=>$auth) {
if($auth['pid'] == 0){
$oneAuth[] = $auth;
}
//存储用户可访问的权限
$visitorAuth[] = strtolower($auth['auth_c'].'/'.$auth['auth_a']);
}
//找出顶级下面的子级权限(2级权限)
foreach($oneAuth as $k=>$auth){
//给每个一级权限添加sonsAuth下标
//去$all_auth所有的权限中去找对应的二级权限
foreach($all_auth as $kk=> $s_auth){
if( $s_auth['pid'] == $auth['auth_id']){
$oneAuth[$k]['sonsAuth'][] = $s_auth;
}
}
}
//保存访问的权限到session中
session('visitorAuth',$visitorAuth);
}
//把权限菜单存储到session中去
session('menuAuth',$oneAuth);
}