关于RBAC的原理讲解在网上可以找到很多,推荐:编程浪子的RBAC讲解,本篇博客就不再累述RBAC的原理到底是什么样的.
传统的权限控制有ACL和RBAC方式,ACL的耦合度很高,扩展性不佳,RBAC很好的解耦合,将权限控制的整个过程涉及的数据表大致分为5张表格:
- user表
- role表
- access表(存储资源数据)
- user_role表
- role_access表
下面是使用原生PHP来实现RBAC权限控制,是一个很简单的例子,因为重在理解这个原理,所以要修改的地方有很多,但是,如果跟着代码一步一步来,那你肯定对RBAC有更深的理解,同时也会发现很多问题,比如多对多的数据表该怎么设计。。。。。
话不多说,先看一下代码吧,代码已经提交到github:https://github.com/searchingbeyond/RBAC-IN-PHP
先看一下目录结构:

然后我会按照便于理解原理的顺序来展示代码:
/RBAC/backend/index.php 后台管理的主页面
1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <title>Document</title> 6</head> 7<body> 8 <div> 9 <table> 10 <tr> 11 <td><a href="https://my.oschina.net//u/4343669/blog/4230070/UserList.php">用户列表</a></td> 12 <td><a href="https://my.oschina.net//u/4343669/blog/4230070/RoleList.php">角色管理</a></td> 13 <td><a href="https://my.oschina.net//u/4343669/blog/4230070/AccessList.php">权限管理</a></td> 14 </tr> 15 <tr> 16 <td><a href="https://my.oschina.net//u/4343669/blog/4230070/AddUser.php">添加用户</a></td> 17 <td><a href="https://my.oschina.net//u/4343669/blog/4230070/AddRole.php">添加角色</a></td> 18 <td><a href="https://my.oschina.net//u/4343669/blog/4230070/AddAccess.php">增加资源</a></td> 19 </tr> 20 </table> 21 </div> 22</body> 23</html>
/RBAC/backend/AddUser.php 添加用户
1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <title>创建用户</title> 6</head> 7<body> 8 <div> 9 <table> 10 <form action="" method="post"> 11 <caption>用户信息</caption> 12 <tr> 13 <td>用户ID:</td> 14 <td><input type="text" name="user_id" style="width:400px"></td> 15 </tr> 16 <tr> 17 <td>用户名:</td> 18 <td><input type="text" name="user_name" style="width:400px"></td> 19 </tr> 20 <tr> 21 <td colspan="2"> 22 <input type="submit" name="adduser" value="添加"> 23 <input type="button" value="返回首页" onclick="location.href='./index.php'"> 24 </td> 25 </tr> 26 </form> 27 </table> 28 </div> 29</body> 30</html> 31 32<?php 33 if( isset($_POST['adduser']) ){ 34 $user_id = $_POST['user_id']; 35 $user_name = $_POST['user_name']; 36 $pdo = new PDO("mysql:host=127.0.0.1;dbname=rbac","root","123456"); 37 $stmt = $pdo->prepare("insert into user (user_id,user_name) values ( :user_id, :user_name )"); 38 $stmt->execute(array(":user_id"=>$user_id,":user_name"=>$user_name)); 39 } 40?>
/RBAC/backend/UserList.php 显示用户列表
1<?php 2 $pdo = new PDO("mysql:host=127.0.0.1;dbname=rbac","root","123456"); 3 $stmt = $pdo->prepare("select * from user"); 4 $stmt->execute(); 5 $user_arr = $stmt->fetchAll(PDO::FETCH_ASSOC); 6 ?> 7 8<!DOCTYPE html> 9<html lang="en"> 10<head> 11 <meta charset="UTF-8"> 12 <title>用户管理</title> 13</head> 14<body> 15 <div> 16 <table> 17 <caption>用户列表</caption> 18 <tr> 19 <td>用户名</td> 20 <td>状态</td> 21 <td>操作</td> 22 </tr> 23 <?php if( count($user_arr) ): ?> 24 <?php foreach($user_arr as $user): ?> 25 <tr> 26 <td><?php echo $user['user_name']; ?></td> 27 <td><?php echo $user['user_status']?"正常":"禁用"; ?></td> 28 <td> 29 <a href="EditUser.php?user_id=<?php echo $user['user_id']?>">角色设置</a> 30 </td> 31 </tr> 32 <?php endforeach; ?> 33 <?php endif; ?> 34 </table> 35 </div> 36</body> 37</html>
/RBAC/backend/EditUser.php 用于设置用户的角色
1<?php 2 if( isset($_GET['user_id']) ){ 3 $pdo = new PDO("mysql:host=127.0.0.1;dbname=rbac","root","123456"); 4 5 //查询用户信息 6 $stmt = $pdo->prepare("select * from user where user_id = :user_id"); 7 $stmt->execute(array("user_id" => $_GET['user_id'])); 8 $user_info = $stmt->fetch(PDO::FETCH_ASSOC); 9 //print_r($user_info); 10 11 //查询用户的角色 12 $stmt = $pdo->prepare("select * from user_role where user_id = :user_id"); 13 $stmt->execute(array(":user_id" => $_GET['user_id'])); 14 //这里只留下role_id 15 $user_role_info = array_column( $stmt->fetchAll(PDO::FETCH_ASSOC),"role_id" ); 16 //print_r($user_role_info); 17 18 //查询所有角色 19 $stmt = $pdo->prepare("select * from role"); 20 $stmt->execute(); 21 $role_arr = $stmt->fetchAll(PDO::FETCH_ASSOC); 22 //print_r($role_arr); 23 } 24 25 //用来判断复选框已选中 26 function checked($i,$arr){ 27 if( in_array($i,$arr) ){ 28 echo "checked"; 29 } 30 } 31 32 ?> 33<!DOCTYPE html> 34<html lang="en"> 35<head> 36 <meta charset="UTF-8"> 37 <title>Document</title> 38</head> 39<body> 40 <div> 41 <form action="" method='post'> 42 <table> 43 <caption>用户信息</caption> 44 <tr> 45 <td>用户名:</td> 46 <td> 47 <input type="hidden" name="user_id" value="<?php echo $user_info['user_id'] ?>"> 48 <?php echo $user_info['user_name']; ?> 49 </td> 50 51 </tr> 52 <tr> 53 <td>角色:</td> 54 <?php if( count($role_arr) ):?> 55 <?php foreach($role_arr as $role): ?> 56 <td> 57 <div> 58 <input type="checkbox" <?php checked($role['role_id'],$user_role_info);?> name="role[]" value="<?php echo $role['role_id'];?>"><?php echo $role['role_name'] ?> 59 </div> 60 </td> 61 <?php endforeach; ?> 62 <?php endif; ?> 63 </tr> 64 <tr> 65 <td colspan="5"> 66 <input type="submit" name="editUser"> 67 <input type="button" value="返回主页" onclick="location.href='./index.php'"> 68 <input type="button" value="返回用户列表" onclick="location.href='./UserList.php'"> 69 </td> 70 </tr> 71 </table> 72 </form> 73 </div> 74</body> 75</html> 76 77 78<?php 79 if( isset($_POST['editUser'])){ 80 //获取传递的role复选框数组,当将全部角色都撤销时,传递的post数据中将不再有role,所以将其设为空数组。 81 $edit_role = isset($_POST['role'])?$_POST['role']:array(); 82 $user_id = $_POST['user_id']; 83 84 //增加的角色: 85 $add_role = array_diff($edit_role,$user_role_info); 86 87 //删除的角色 88 $sub_role = array_diff($user_role_info,$edit_role); 89 90 //执行删除角色 91 $stmt = $pdo->prepare("delete from user_role where user_id = :user_id and role_id = :role_id"); 92 foreach($sub_role as $role_id){ 93 $stmt->execute(array(":user_id"=>$user_id,":role_id"=>$role_id)); 94 } 95 96 //执行增加角色 97 $stmt = $pdo->prepare("insert into user_role (user_id,role_id) values(:user_id,:role_id)"); 98 foreach($add_role as $role_id){ 99 $stmt->execute(array(":user_id"=>$user_id,":role_id"=>$role_id)); 100 } 101 102 // echo "<script>location.href='editUser.php?user_id=$user_id</script>"; 103 echo "<script>location.replace(location.href);</script>"; 104 } 105 ?>
其实看到这里,大概就知道RBAC是怎么简单实现的了,下面几个文件和上面的文件很相似;
/RBAC/backend/AddRole.php 添加一个角色
1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <title>创建角色</title> 6</head> 7<body> 8 <div id="container"> 9 <table> 10 <form action="" method="post"> 11 <caption>角色信息</caption> 12 <tr> 13 <td>角色名:</td> 14 <td><input type="text" name="role_name" style="width:300px"></td> 15 </tr> 16 <tr> 17 <td colspan="2"> 18 <input type="submit" name="addrole" value="添加"> 19 <input type="button" value="返回首页" onclick="location.href='./index.php'"> 20 </td> 21 </tr> 22 </form> 23 </table> 24 </div> 25</body> 26</html> 27 28 29<?php 30 if( isset($_POST['addrole']) ){ 31 $role_name = $_POST['role_name']; 32 $pdo = new PDO("mysql:host=127.0.0.1;dbname=rbac","root","123456"); 33 $stmt = $pdo->prepare("insert into role (role_name) values ( :role_name )"); 34 $stmt->execute(array(":role_name"=>$role_name)); 35 } 36 ?>
/RBAC/backend/RoleList.php 显示角色的列表
1<?php 2 $pdo = new PDO("mysql:host=127.0.0.1;dbname=rbac","root","123456"); 3 $stmt = $pdo->prepare("select * from role"); 4 $stmt->execute(); 5 $role_arr = $stmt->fetchAll(PDO::FETCH_ASSOC); 6 ?> 7 8<!DOCTYPE html> 9<html lang="en"> 10<head> 11 <meta charset="UTF-8"> 12 <title>角色管理</title> 13</head> 14<body> 15 <div> 16 <table> 17 <caption>角色列表</caption> 18 <tr> 19 <td>角色名</td> 20 <td>操作</td> 21 </tr> 22 <?php if( count($role_arr) ): ?> 23 <?php foreach($role_arr as $role): ?> 24 <tr> 25 <td><?php echo $role['role_name']; ?></td> 26 <td> 27 <a href="EditRole.php?role_id=<?php echo $role['role_id']?>">权限设置</a> 28 </td> 29 </tr> 30 <?php endforeach; ?> 31 <?php endif; ?> 32 </table> 33 </div> 34</body> 35</html>
/RBAC/backend/EditRole.php 编辑角色可以获得哪些资源(权限access)
1<?php 2 if( isset($_GET['role_id']) ){ 3 $pdo = new PDO("mysql:host=127.0.0.1;dbname=rbac","root","123456"); 4 5 //查询角色信息 6 $stmt = $pdo->prepare("select * from role where role_id = :role_id"); 7 $stmt->execute(array("role_id" => $_GET['role_id'])); 8 $role_info = $stmt->fetch(PDO::FETCH_ASSOC); 9 //print_r($user_info); 10 11 //查询当前角色拥有的权限 12 $stmt = $pdo->prepare("select * from role_access where role_id = :role_id"); 13 $stmt->execute(array(":role_id" => $_GET['role_id'])); 14 //这里只留下access_id 15 $role_access_info = array_column( $stmt->fetchAll(PDO::FETCH_ASSOC),"access_id" ); 16 //print_r($user_role_info); 17 18 //查询所有的资源信息 19 $stmt = $pdo->prepare("select * from access"); 20 $stmt->execute(); 21 $access_arr = $stmt->fetchAll(PDO::FETCH_ASSOC); 22 //print_r($role_arr); 23 } 24 25 //用来判断复选框已选中 26 function checked($i,$arr){ 27 if( in_array($i,$arr) ){ 28 echo "checked"; 29 } 30 } 31 32 ?> 33<!DOCTYPE html> 34<html lang="en"> 35<head> 36 <meta charset="UTF-8"> 37 <title>编辑角色</title> 38</head> 39<body> 40 <div style="width:400px;margin:0 auto"> 41 <form action="" method='post'> 42 <table> 43 <caption>角色信息</caption> 44 <tr> 45 <td>角色名:</td> 46 <td> 47 <input type="hidden" name="role_id" value="<?php echo $role_info['role_id'] ?>"> 48 <?php echo $role_info['role_name']; ?> 49 </td> 50 51 </tr> 52 <tr> 53 <td>权限:</td> 54 <?php if( count($access_arr) ):?> 55 <td> 56 <?php foreach($access_arr as $access): ?> 57 <div> 58 <input type="checkbox" <?php checked($access['access_id'],$role_access_info);?> name="access[]" value="<?php echo $access['access_id']?>"><?php echo $access['title'] ?> 59 </div> 60 <?php endforeach; ?> 61 </td> 62 <?php endif; ?> 63 </tr> 64 <tr> 65 <td colspan="5"> 66 <input type="submit" name="editRole"> 67 <input type="button" value="返回主页" onclick="location.href='./index.php'"> 68 <input type="button" value="返回角色列表" onclick="location.href='./RoleList.php'"> 69 </td> 70 </tr> 71 </table> 72 </form> 73 </div> 74</body> 75</html> 76 77 78<?php 79 if( isset($_POST['editRole'])){ 80 //获取传递的role复选框数组,当将全部角色都撤销时,传递的post数据中将不再有role,所以将其设为空数组。 81 $access = isset($_POST['access'])?$_POST['access']:array(); 82 $role_id = $_POST['role_id']; 83 84 //增加的角色: 85 $add_access = array_diff($access,$role_access_info); 86 87 //删除的角色 88 $sub_access = array_diff($role_access_info,$access); 89 90 //执行删除角色 91 $stmt = $pdo->prepare("delete from role_access where role_id = :role_id and access_id = :access_id"); 92 foreach($sub_access as $access_id){ 93 $stmt->execute(array(":role_id"=>$role_id,":access_id"=>$access_id )); 94 } 95 96 //执行增加角色 97 $stmt = $pdo->prepare("insert into role_access (role_id,access_id) values(:role_id,:access_id)"); 98 foreach($add_access as $access_id){ 99 $stmt->execute(array(":role_id"=>$role_id,":access_id"=>$access_id )); 100 } 101 102 echo "<script>location.replace(location.href);</script>"; 103 } 104 ?>
/RBAC/backend/AddAccess.php 添加一个权限(资源)
1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <title>创建访问资源</title> 6</head> 7<body> 8 <div> 9 <table> 10 <form action="" method="post"> 11 <caption>资源信息</caption> 12 <tr> 13 <td>资源名:</td> 14 <td><input type="text" name="title" style="width:400px"></td> 15 </tr> 16 <tr> 17 <td>URLs:</td> 18 <td> 19 <textarea style="margin-top:20px;width:400px;height:200px" name="urls"></textarea> 20 </td> 21 </tr> 22 <tr> 23 <td colspan="2"> 24 <input type="submit" name="addaccess" value="添加"> 25 <input type="button" value="返回首页" onclick="location.href='./index.php'"> 26 </td> 27 </tr> 28 </form> 29 </table> 30 </div> 31</body> 32</html> 33 34 35<?php 36 37 if( isset($_POST['addaccess']) ){ 38 $title = $_POST['title']; 39 $urls = json_encode( explode(",",$_POST['urls']) ); 40 $pdo = new PDO("mysql:host=127.0.0.1;dbname=rbac","root","123456"); 41 $stmt = $pdo->prepare("insert into access (title,urls) values ( :title, :urls )"); 42 $stmt->execute(array(":title"=>$title,":urls"=>$urls)); 43 } 44 ?>
/RBAC/backend/AccessList.php 资源(权限)列表
1<?php 2 $pdo = new PDO("mysql:host=127.0.0.1;dbname=rbac","root","123456"); 3 $stmt = $pdo->prepare("select * from access"); 4 $stmt->execute(); 5 $access_arr = $stmt->fetchAll(PDO::FETCH_ASSOC); 6 7 function print_json($data){ 8 $arr = json_decode($data,true); 9 foreach($arr as $v ){ 10 echo $v."<br>"; 11 } 12 } 13 ?> 14 15<!DOCTYPE html> 16<html lang="en"> 17<head> 18 <meta charset="UTF-8"> 19 <title>资源管理</title> 20</head> 21<body> 22 <div id="container"> 23 <table> 24 <caption>资源列表</caption> 25 <tr> 26 <td>资源ID</td> 27 <td>名称</td> 28 <td>urls</td> 29 <td>操作</td> 30 </tr> 31 <?php if( count($access_arr) ): ?> 32 <?php foreach($access_arr as $access): ?> 33 <tr> 34 <td><?php echo $access['access_id']; ?></td> 35 <td><?php echo $access['title']; ?></td> 36 <td><?php print_json($access['urls']); ?></td> 37 <td> 38 <a href="EditAccess.php?access_id=<?php echo $access['access_id']?>">资源设置</a> 39 </td> 40 </tr> 41 <?php endforeach; ?> 42 <?php endif; ?> 43 </table> 44 </div> 45</body> 46</html>
到这里,基本的权限系统就已经建立好了,但是缺少实例,而且还有检测权限的操作。
/RBAC/frontend/index.php 前台首页
1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <title>首页</title> 6</head> 7<body> 8 <h2><a href="https://my.oschina.net//u/4343669/blog/4230070/pageone.php">pageone.php</a></h2> 9 <h2><a href="https://my.oschina.net//u/4343669/blog/4230070/pagetwo.php">pagetwo.php</a></h2> 10 <h2><a href="https://my.oschina.net//u/4343669/blog/4230070/pagethree.php">pagethree.php</a></h2> 11</body> 12</html>
/RBAC/frontend/pageone.php 测试页面1
1<?php include "checkPermission.php" ?> 2<!DOCTYPE html> 3<html lang="en"> 4<head> 5 <meta charset="UTF-8"> 6 <title>pageOne</title> 7</head> 8<body> 9 <h1>pageOne</h1> 10 <input type="button" onclick="history.back()" value="返回"> 11</body> 12</html>
/RBAC/frontend/pagetwo.php 测试页面2
1<?php include "checkPermission.php" ?> 2<!DOCTYPE html> 3<html lang="en"> 4<head> 5 <meta charset="UTF-8"> 6 <title>pageTwo</title> 7</head> 8<body> 9 <h1>pageTwo</h1> 10 <input type="button" onclick="history.back()" value="返回"> 11</body> 12</html>
/RBAC/frontend/pagethree.php 测试页面3
1<?php include "checkPermission.php" ?> 2<!DOCTYPE html> 3<html lang="en"> 4<head> 5 <meta charset="UTF-8"> 6 <title>pageThree</title> 7</head> 8<body> 9 <h1>pageThree</h1> 10 <input type="button" onclick="history.back()" value="返回"> 11</body> 12</html>
/RBAC/frontend/checkPermission.php 检测权限
1<?php 2 //此处为了测试,不进行检查 3// 真正使用的时候,应该使用下面一个php代码 4 $user_id="666"; 5 $user_name="beyond"; 6 $pdo = new PDO("mysql:host=127.0.0.1;dbname=rbac","root","123456"); 7 $stmt = $pdo->prepare("select count(*) from user where user_id=:user_id and user_name=:user_name;"); 8 $stmt->execute(array(":user_id"=>$user_id,":user_name"=>$user_name)); 9 $flag = $stmt->fetch(PDO::FETCH_ASSOC); 10 11 !$flag && die("无此用户"); 12 13 $stmt = $pdo->prepare("select * from user 14 left join user_role on user.user_id = user_role.user_id 15 right join role_access on user_role.role_id = role_access.role_id 16 left join access on access.access_id = role_access.access_id 17 where user.user_id = :user_id 18 "); 19 $stmt->execute(array(":user_id"=>$user_id)); 20 21 $data = $stmt->fetchAll(PDO::FETCH_ASSOC); 22 23 //urls用来保存当前用户所拥有的权限url 24 //可以保存在缓存中,提高效率 25 $urls = array(); 26 foreach( $data as $v ){ 27 $urls[] = json_decode($v['urls'],true)[0]; 28 } 29 30 //判断对当前url是否有权限 31 if ( ! in_array( $_SERVER['SCRIPT_NAME'], $urls ) ){ 32 include "error.html"; 33 exit; 34 } 35 ?> 36 37 38 <?php 39 /* 40 41 真正使用的时候,应该使用这个 42 43 //因为重点不是验证登录,所以这里简单的验证登录 44 if( $_SESSION['user_id'] && $_SESSION['user_name'] ){ 45 $pdo = new PDO("mysql:host=127.0.0.1;dbname=rbac","root","123456"); 46 $stmt = $pdo->prepare("select count(*) from user where user_id=:user_id and user_name=:user_name;"); 47 $stmt->execute(array(":user_id"=>$_SESSION['user_id'],":user_name"=>$_SESSION['user_name'])); 48 $flag = $stmt->fetch(PDO::FETCH_ASSOC); 49 !$flag && die("无此用户"); 50 $stmt = $pdo->prepare("select * from user 51 left join user_role on user.user_id = user_role.user_id 52 right join role_access on user_role.role_id = role_access.role_id 53 left join access on access.access_id = role_access.access_id 54 where user.user_id = :user_id 55 "); 56 $stmt->execute(array("user_id"=>$_SESSION['user_id'])); 57 $data = $stmt->fetchAll(PDO::FETCH_ASSOC); 58 $urls = array(); 59 foreach( $data as $v ){ 60 $urls[] = json_decode($v['urls'],true)[0]; 61 } 62 if ( ! in_array( $_SERVER['SCRIPT_NAME'], $urls ) ){ 63 include "error.html"; 64 exit; 65 } 66 67 } 68 69*/ 70 ?>
/RBAC/frontend/error.html 没有权限时显示
1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <title>无权限</title> 6</head> 7<body> 8 <h2>你没有权限查看该页面,请联系管理员开通</h2> 9</body> 10</html>