RBAC权限管理及使用原生PHP实现

Wesley13
• 阅读 578

  关于RBAC的原理讲解在网上可以找到很多,推荐:编程浪子的RBAC讲解,本篇博客就不再累述RBAC的原理到底是什么样的.

   传统的权限控制有ACL和RBAC方式,ACL的耦合度很高,扩展性不佳,RBAC很好的解耦合,将权限控制的整个过程涉及的数据表大致分为5张表格:

  1. user表
  2. role表
  3. access表(存储资源数据)
  4. user_role表
  5. role_access表

 

  下面是使用原生PHP来实现RBAC权限控制,是一个很简单的例子,因为重在理解这个原理,所以要修改的地方有很多,但是,如果跟着代码一步一步来,那你肯定对RBAC有更深的理解,同时也会发现很多问题,比如多对多的数据表该怎么设计。。。。。

  话不多说,先看一下代码吧,代码已经提交到github:https://github.com/searchingbeyond/RBAC-IN-PHP

  先看一下目录结构:

  RBAC权限管理及使用原生PHP实现

然后我会按照便于理解原理的顺序来展示代码:

 /RBAC/backend/index.php     后台管理的主页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div>
        <table>
            <tr>
                <td><a href="https://my.oschina.net//u/4343669/blog/4230070/UserList.php">用户列表</a></td>
                <td><a href="https://my.oschina.net//u/4343669/blog/4230070/RoleList.php">角色管理</a></td>
                <td><a href="https://my.oschina.net//u/4343669/blog/4230070/AccessList.php">权限管理</a></td>
            </tr>
            <tr>
                <td><a href="https://my.oschina.net//u/4343669/blog/4230070/AddUser.php">添加用户</a></td>
                <td><a href="https://my.oschina.net//u/4343669/blog/4230070/AddRole.php">添加角色</a></td>
                <td><a href="https://my.oschina.net//u/4343669/blog/4230070/AddAccess.php">增加资源</a></td>
            </tr>
        </table>
    </div>
</body>
</html>

/RBAC/backend/AddUser.php    添加用户

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>创建用户</title>
</head>
<body>
    <div>
        <table>
            <form action="" method="post">
            <caption>用户信息</caption>
            <tr>
                <td>用户ID:</td>
                <td><input type="text" name="user_id" style="width:400px"></td>
            </tr>
            <tr>
                <td>用户名:</td>
                <td><input type="text" name="user_name" style="width:400px"></td>
            </tr>
            <tr>
                <td colspan="2">
                    <input type="submit" name="adduser" value="添加">
                    <input type="button" value="返回首页" onclick="location.href='./index.php'">
                </td>
            </tr>
            </form>
        </table>
    </div>
</body>
</html>

<?php 
    if( isset($_POST['adduser']) ){
        $user_id = $_POST['user_id'];
        $user_name = $_POST['user_name'];
        $pdo = new PDO("mysql:host=127.0.0.1;dbname=rbac","root","123456");
        $stmt = $pdo->prepare("insert into user (user_id,user_name) values ( :user_id, :user_name )");
        $stmt->execute(array(":user_id"=>$user_id,":user_name"=>$user_name));
    }
?>

/RBAC/backend/UserList.php     显示用户列表

<?php 
    $pdo = new PDO("mysql:host=127.0.0.1;dbname=rbac","root","123456");
    $stmt = $pdo->prepare("select * from user");
    $stmt->execute();
    $user_arr = $stmt->fetchAll(PDO::FETCH_ASSOC);
 ?>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户管理</title>
</head>
<body>
    <div>
        <table>
            <caption>用户列表</caption>
            <tr>
                <td>用户名</td>
                <td>状态</td>
                <td>操作</td>
            </tr>
            <?php if( count($user_arr) ): ?>
                <?php foreach($user_arr as $user): ?>
                     <tr>
                         <td><?php echo $user['user_name']; ?></td>
                         <td><?php echo $user['user_status']?"正常":"禁用"; ?></td>
                         <td>
                             <a href="EditUser.php?user_id=<?php echo $user['user_id']?>">角色设置</a>
                         </td>
                     </tr>
                <?php endforeach; ?>
            <?php endif; ?>
        </table>
    </div>
</body>
</html>

/RBAC/backend/EditUser.php      用于设置用户的角色

<?php 
    if( isset($_GET['user_id']) ){
        $pdo = new PDO("mysql:host=127.0.0.1;dbname=rbac","root","123456");

        //查询用户信息
        $stmt = $pdo->prepare("select * from user where user_id = :user_id");
        $stmt->execute(array("user_id" => $_GET['user_id']));
        $user_info = $stmt->fetch(PDO::FETCH_ASSOC);
        //print_r($user_info);

        //查询用户的角色
        $stmt = $pdo->prepare("select * from user_role where user_id = :user_id");
        $stmt->execute(array(":user_id" => $_GET['user_id']));
        //这里只留下role_id
        $user_role_info = array_column( $stmt->fetchAll(PDO::FETCH_ASSOC),"role_id" );
        //print_r($user_role_info);

        //查询所有角色
        $stmt = $pdo->prepare("select * from role");
        $stmt->execute();
        $role_arr = $stmt->fetchAll(PDO::FETCH_ASSOC);
        //print_r($role_arr);
    }

    //用来判断复选框已选中
    function checked($i,$arr){
        if( in_array($i,$arr) ){
            echo "checked";
        }
    }

 ?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div>
    <form action="" method='post'>
        <table>
            <caption>用户信息</caption>
            <tr>
                <td>用户名:</td>
                <td>
                    <input type="hidden" name="user_id" value="<?php echo $user_info['user_id'] ?>">
                    <?php echo $user_info['user_name']; ?>
                </td>
                
            </tr>
            <tr>
                <td>角色:</td>
                <?php if( count($role_arr) ):?>
                    <?php foreach($role_arr as $role): ?>    
                        <td>
                            <div>
            <input type="checkbox" <?php checked($role['role_id'],$user_role_info);?> name="role[]" value="<?php echo $role['role_id'];?>"><?php echo $role['role_name'] ?>
                            </div>
                        </td>
                    <?php endforeach; ?>
                <?php endif; ?>
            </tr>
            <tr>
                <td colspan="5">
                    <input type="submit" name="editUser">
                    <input type="button" value="返回主页" onclick="location.href='./index.php'">
                    <input type="button" value="返回用户列表" onclick="location.href='./UserList.php'">
                </td>
            </tr>
        </table>
    </form>    
    </div>
</body>
</html>


<?php 
    if( isset($_POST['editUser'])){
        //获取传递的role复选框数组,当将全部角色都撤销时,传递的post数据中将不再有role,所以将其设为空数组。
        $edit_role = isset($_POST['role'])?$_POST['role']:array();
        $user_id = $_POST['user_id'];

        //增加的角色:
        $add_role = array_diff($edit_role,$user_role_info);

        //删除的角色
        $sub_role = array_diff($user_role_info,$edit_role);

        //执行删除角色
        $stmt = $pdo->prepare("delete from user_role where user_id = :user_id and role_id = :role_id");
        foreach($sub_role as $role_id){        
            $stmt->execute(array(":user_id"=>$user_id,":role_id"=>$role_id));    
        }

        //执行增加角色
        $stmt = $pdo->prepare("insert into user_role (user_id,role_id) values(:user_id,:role_id)");
        foreach($add_role as $role_id){
            $stmt->execute(array(":user_id"=>$user_id,":role_id"=>$role_id));
        }

        // echo "<script>location.href='editUser.php?user_id=$user_id</script>";
        echo "<script>location.replace(location.href);</script>";
    }
 ?>

其实看到这里,大概就知道RBAC是怎么简单实现的了,下面几个文件和上面的文件很相似;

/RBAC/backend/AddRole.php      添加一个角色

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>创建角色</title>
</head>
<body>
    <div id="container">
        <table>
            <form action="" method="post">
            <caption>角色信息</caption>
            <tr>
                <td>角色名:</td>
                <td><input type="text" name="role_name" style="width:300px"></td>
            </tr>
            <tr>
                <td colspan="2">
                    <input type="submit" name="addrole" value="添加">
                    <input type="button" value="返回首页" onclick="location.href='./index.php'">
                </td>
            </tr>
            </form>
        </table>
    </div>
</body>
</html>


<?php 
    if( isset($_POST['addrole']) ){
        $role_name = $_POST['role_name'];
        $pdo = new PDO("mysql:host=127.0.0.1;dbname=rbac","root","123456");
        $stmt = $pdo->prepare("insert into role (role_name) values ( :role_name )");
        $stmt->execute(array(":role_name"=>$role_name));
    }
 ?>

/RBAC/backend/RoleList.php  显示角色的列表

<?php 
    $pdo = new PDO("mysql:host=127.0.0.1;dbname=rbac","root","123456");
    $stmt = $pdo->prepare("select * from role");
    $stmt->execute();
    $role_arr = $stmt->fetchAll(PDO::FETCH_ASSOC);
 ?>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>角色管理</title>
</head>
<body>
    <div>
        <table>
            <caption>角色列表</caption>
            <tr>
                <td>角色名</td>
                <td>操作</td>
            </tr>
            <?php if( count($role_arr) ): ?>
                <?php foreach($role_arr as $role): ?>
                     <tr>
                         <td><?php echo $role['role_name']; ?></td>
                         <td>
                             <a href="EditRole.php?role_id=<?php echo $role['role_id']?>">权限设置</a>
                         </td>
                     </tr>
                <?php endforeach; ?>
            <?php endif; ?>
        </table>
    </div>
</body>
</html>

/RBAC/backend/EditRole.php     编辑角色可以获得哪些资源(权限access)

<?php 
    if( isset($_GET['role_id']) ){
        $pdo = new PDO("mysql:host=127.0.0.1;dbname=rbac","root","123456");

        //查询角色信息
        $stmt = $pdo->prepare("select * from role where role_id = :role_id");
        $stmt->execute(array("role_id" => $_GET['role_id']));
        $role_info = $stmt->fetch(PDO::FETCH_ASSOC);
        //print_r($user_info);

        //查询当前角色拥有的权限
        $stmt = $pdo->prepare("select * from role_access where role_id = :role_id");
        $stmt->execute(array(":role_id" => $_GET['role_id']));
        //这里只留下access_id
        $role_access_info = array_column( $stmt->fetchAll(PDO::FETCH_ASSOC),"access_id" );
        //print_r($user_role_info);

        //查询所有的资源信息
        $stmt = $pdo->prepare("select * from access");
        $stmt->execute();
        $access_arr = $stmt->fetchAll(PDO::FETCH_ASSOC);
        //print_r($role_arr);
    }

    //用来判断复选框已选中
    function checked($i,$arr){
        if( in_array($i,$arr) ){
            echo "checked";
        }
    }

 ?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>编辑角色</title>
</head>
<body>
    <div style="width:400px;margin:0 auto">
    <form action="" method='post'>
        <table>
            <caption>角色信息</caption>
            <tr>
                <td>角色名:</td>
                <td>
                    <input type="hidden" name="role_id" value="<?php echo $role_info['role_id'] ?>">
                    <?php echo $role_info['role_name']; ?>
                </td>
                
            </tr>
            <tr>
                <td>权限:</td>
                <?php if( count($access_arr) ):?>
                        <td>
                    <?php foreach($access_arr as $access): ?>    
                            <div>
            <input type="checkbox" <?php checked($access['access_id'],$role_access_info);?> name="access[]" value="<?php echo $access['access_id']?>"><?php echo $access['title'] ?>
                            </div>
                    <?php endforeach; ?>
                        </td>
                <?php endif; ?>
            </tr>
            <tr>
                <td colspan="5">
                    <input type="submit" name="editRole">
                    <input type="button" value="返回主页" onclick="location.href='./index.php'">
                    <input type="button" value="返回角色列表" onclick="location.href='./RoleList.php'">
                </td>
            </tr>
        </table>
    </form>    
    </div>
</body>
</html>


<?php 
    if( isset($_POST['editRole'])){
        //获取传递的role复选框数组,当将全部角色都撤销时,传递的post数据中将不再有role,所以将其设为空数组。
        $access = isset($_POST['access'])?$_POST['access']:array();
        $role_id = $_POST['role_id'];

        //增加的角色:
        $add_access = array_diff($access,$role_access_info);

        //删除的角色
        $sub_access = array_diff($role_access_info,$access);

        //执行删除角色
        $stmt = $pdo->prepare("delete from role_access where role_id = :role_id and access_id = :access_id");
        foreach($sub_access as $access_id){        
            $stmt->execute(array(":role_id"=>$role_id,":access_id"=>$access_id ));    
        }

        //执行增加角色
        $stmt = $pdo->prepare("insert into role_access (role_id,access_id) values(:role_id,:access_id)");
        foreach($add_access as $access_id){
            $stmt->execute(array(":role_id"=>$role_id,":access_id"=>$access_id ));    
        }

        echo "<script>location.replace(location.href);</script>";
    }
 ?>

/RBAC/backend/AddAccess.php  添加一个权限(资源)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>创建访问资源</title>
</head>
<body>
    <div>
        <table>
            <form action="" method="post">
            <caption>资源信息</caption>
            <tr>
                <td>资源名:</td>
                <td><input type="text" name="title" style="width:400px"></td>
            </tr>
            <tr>
                <td>URLs:</td>
                <td>
                    <textarea style="margin-top:20px;width:400px;height:200px" name="urls"></textarea>
                </td>
            </tr>
            <tr>
                <td colspan="2">
                    <input type="submit" name="addaccess" value="添加">
                    <input type="button" value="返回首页" onclick="location.href='./index.php'">
                </td>
            </tr>
            </form>
        </table>
    </div>
</body>
</html>


<?php 

    if( isset($_POST['addaccess']) ){
        $title = $_POST['title'];
        $urls = json_encode( explode(",",$_POST['urls']) );
        $pdo = new PDO("mysql:host=127.0.0.1;dbname=rbac","root","123456");
        $stmt = $pdo->prepare("insert into access (title,urls) values ( :title, :urls )");
        $stmt->execute(array(":title"=>$title,":urls"=>$urls));
    }
 ?>

/RBAC/backend/AccessList.php  资源(权限)列表

<?php 
    $pdo = new PDO("mysql:host=127.0.0.1;dbname=rbac","root","123456");
    $stmt = $pdo->prepare("select * from access");
    $stmt->execute();
    $access_arr = $stmt->fetchAll(PDO::FETCH_ASSOC);

    function print_json($data){
        $arr = json_decode($data,true);
        foreach($arr as $v ){
            echo $v."<br>";
        }
    }
 ?>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>资源管理</title>
</head>
<body>
    <div id="container">
        <table>
            <caption>资源列表</caption>
            <tr>
                <td>资源ID</td>
                <td>名称</td>
                <td>urls</td>
                <td>操作</td>
            </tr>
            <?php if( count($access_arr) ): ?>
                <?php foreach($access_arr as $access): ?>
                     <tr>
                         <td><?php echo $access['access_id']; ?></td>
                         <td><?php echo $access['title']; ?></td>
                         <td><?php print_json($access['urls']); ?></td>
                         <td>
                             <a href="EditAccess.php?access_id=<?php echo $access['access_id']?>">资源设置</a>
                         </td>
                     </tr>
                <?php endforeach; ?>
            <?php endif; ?>
        </table>
    </div>
</body>
</html>

到这里,基本的权限系统就已经建立好了,但是缺少实例,而且还有检测权限的操作。

/RBAC/frontend/index.php  前台首页

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
    <h2><a href="https://my.oschina.net//u/4343669/blog/4230070/pageone.php">pageone.php</a></h2>
    <h2><a href="https://my.oschina.net//u/4343669/blog/4230070/pagetwo.php">pagetwo.php</a></h2>
    <h2><a href="https://my.oschina.net//u/4343669/blog/4230070/pagethree.php">pagethree.php</a></h2>
</body>
</html>

/RBAC/frontend/pageone.php  测试页面1

<?php include "checkPermission.php" ?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>pageOne</title>
</head>
<body>
    <h1>pageOne</h1>
    <input type="button" onclick="history.back()" value="返回">
</body>
</html>

/RBAC/frontend/pagetwo.php   测试页面2

<?php include "checkPermission.php" ?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>pageTwo</title>
</head>
<body>
    <h1>pageTwo</h1>
    <input type="button" onclick="history.back()" value="返回">
</body>
</html>

/RBAC/frontend/pagethree.php  测试页面3

<?php include "checkPermission.php" ?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>pageThree</title>
</head>
<body>
    <h1>pageThree</h1>
    <input type="button" onclick="history.back()" value="返回">
</body>
</html>

/RBAC/frontend/checkPermission.php  检测权限

<?php 
    //此处为了测试,不进行检查
// 真正使用的时候,应该使用下面一个php代码
    $user_id="666";
    $user_name="beyond";
    $pdo = new PDO("mysql:host=127.0.0.1;dbname=rbac","root","123456");
    $stmt = $pdo->prepare("select count(*) from user where user_id=:user_id and user_name=:user_name;");
    $stmt->execute(array(":user_id"=>$user_id,":user_name"=>$user_name));
    $flag = $stmt->fetch(PDO::FETCH_ASSOC);

    !$flag && die("无此用户");

    $stmt = $pdo->prepare("select * from user
                left join user_role on user.user_id = user_role.user_id
                right join role_access on user_role.role_id = role_access.role_id
                left join access on access.access_id = role_access.access_id
                where user.user_id = :user_id
                ");
    $stmt->execute(array(":user_id"=>$user_id));

    $data = $stmt->fetchAll(PDO::FETCH_ASSOC);

    //urls用来保存当前用户所拥有的权限url
    //可以保存在缓存中,提高效率
    $urls = array();
    foreach( $data as $v ){
        $urls[] = json_decode($v['urls'],true)[0];
    }

    //判断对当前url是否有权限
    if ( ! in_array( $_SERVER['SCRIPT_NAME'], $urls ) ){
        include "error.html";
        exit;
    } 
 ?>


 <?php 
 /*

     真正使用的时候,应该使用这个

     //因为重点不是验证登录,所以这里简单的验证登录
    if( $_SESSION['user_id'] && $_SESSION['user_name'] ){
        $pdo = new PDO("mysql:host=127.0.0.1;dbname=rbac","root","123456");
        $stmt = $pdo->prepare("select count(*) from user where user_id=:user_id and user_name=:user_name;");
        $stmt->execute(array(":user_id"=>$_SESSION['user_id'],":user_name"=>$_SESSION['user_name']));
        $flag = $stmt->fetch(PDO::FETCH_ASSOC);
        !$flag && die("无此用户");
        $stmt = $pdo->prepare("select * from user
                    left join user_role on user.user_id = user_role.user_id
                    right join role_access on user_role.role_id = role_access.role_id
                    left join access on access.access_id = role_access.access_id
                    where user.user_id = :user_id
                    ");
        $stmt->execute(array("user_id"=>$_SESSION['user_id']));
        $data = $stmt->fetchAll(PDO::FETCH_ASSOC);
        $urls = array();
        foreach( $data as $v ){
            $urls[] = json_decode($v['urls'],true)[0];
        }
        if ( ! in_array( $_SERVER['SCRIPT_NAME'], $urls ) ){
            include "error.html";
            exit;
        } 

    }

*/
 ?>

/RBAC/frontend/error.html      没有权限时显示

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>无权限</title>
</head>
<body>
    <h2>你没有权限查看该页面,请联系管理员开通</h2>
</body>
</html>
点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
2年前
RBAC 权限模型
RBAC0模型最基本的!(https://oscimg.oschina.net/oscnet/80a47f70d0c945ab47cadc28f63f77ab39d.png)MySQL脚本,没有建立外键约束。!(https://oscimg.oschina.net/oscnet/407d0ad45bb6f39
Wesley13 Wesley13
2年前
RBAC模型
RBAC基于角色的访问控制(RoleBasedAccessControl)作为传统访问控制(自主访问,强制访问)的有前景的代替受到广泛的关注。在RBAC中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。在一个组织中,角色是为了完成各种工作而创造,用户则依据它的责任和资格来被指派相应的角色,用
Wesley13 Wesley13
2年前
RBAC基于角色的权限管理
RBAC基于角色的权限管理设计篇1.1RBAC基于角色的权限管理设计篇1.0https://my.oschina.net/xiaozhutefannao/blog/1600612(https://my.oschina.net/xiaozhutefann
Wesley13 Wesley13
2年前
PHP RBAC权限控制,基于CI框架(版本3.1.9)
2018年11月7日更新:目前功能已做到事件级别权限控制,如:后台用户的添加操作、删除操作和保存操作等具体到事件级的操作方法有权限则展示相应的操作菜单,没权限则隐藏相应菜单或提示无权限到目前算是真正做到了每一步操作都控制相应的权限,防止错误操作基于CI框架(版本3.1.9)(hmvc模式)的RBAC权限管理,目前功能为:后台菜单全动态
Stella981 Stella981
2年前
Apache Sentry实战之旅(一)—— Impala+Sentry整合
Impala默认是以impala这个超级用户运行服务,执行DML和DDL操作的,要实现不同用户之间细粒度的权限控制,需要与Sentry整合。Sentry是Apache下的一个开源项目,它基于RBAC的授权模型实现了权限控制,Impala与它整合以后,就能实现不同用户之间在应用层的权限认证,从而控制用户的DML、DDL
Stella981 Stella981
2年前
Spring Security 新特性 Lambda DSL 使用
\项目推荐:SpringCloud、SpringSecurityOAuth2的RBAC权限管理系统欢迎关注(https://gitee.com/log4j/pig)LambdaDSL概述SpringSecurity5.2对LambdaDSL语法的增强,允许使用lambda配置HttpSec
Stella981 Stella981
2年前
PHP Laravel5实现的RBAC权限管理操作示例
根据不同的权限,在菜单栏显示不同的功能,只对菜单进行了限制,若对路由也进行限制,可以根据菜单的例子,请自行完善,开发。下面请认真学习一下laravel的RBAC设计1、建表(用户表、角色表、权限表、用户角色表、角色权限表)1CREATETABLEIFNOTEXISTSmr_role2(3id
Stella981 Stella981
2年前
Harbor+Helm Chart构建k8s应用程序打包存储发布的基础环境
Harbor简介Harbor是由VMware公司中国团队为企业用户设计的Registryserver开源项目,包括了权限管理(RBAC)、LDAP、审计、管理界面、自我注册、HA、RESTfulAPI等企业必需的功能,属于CloudNativeComputingFoundation(CNCF,云原生计算基金会)的毕业
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究