0

0

RBAC权限控制实现原理——权限表、用户表与关联表设计

灭绝师太

灭绝师太

发布时间:2021-08-31 17:11:59

|

6767人浏览过

|

来源于php中文网

原创

        rbac是英文role-based access control的首字母缩写,中文意思是基础角色的权限控制,它是一种思想,根据 rbac 思想进行数据表设计,更好的完成不同角色的对应的权限控制。

        

        

        如何使用RBAC思想进行数据表的设计 


        如果我们的项目允许一个后台管理用户可能有1个或者2个及2个以上的多个角色,按照下面进行设计:


11.png

    权限菜单表,角色表,用户表是互相独立的。设计表的顺序是权限菜单表,角色表,用户表,用户-角色关联表。

    1. 首先是权限菜单表设计如下:

    注意:权限菜单可以是多级菜单,添加pid字段,方便无限极递归分类。 

11.png

    2. 角色表设计如下:

11.png

mall电商系统
mall电商系统

mall项目是一套电商系统,包括前台商城系统及后台管理系统,基于SpringBoot+MyBatis实现,采用Docker容器化部署。前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程、会员中心、客户服务、帮助中心等模块。后台管理系统包含商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等模块。

下载

    3. 用户表设计如下:

11.png

    4. 最后是用户-角色关联表设计如下:

11.png    

    

    当超级管理员在后台需要添加新用户时,不仅需要insert数据进用户表,也需要在用户-角色表中添加用户和角色的关系。与之对应,删除用户时,也需要将用户-角色表中对应的用户-角色关系删除。

public function add()
    {

        if(request()->isPost()){
            $role_id = input('post.role_id');
            $data = [
                'uname'=>input('post.uname'),
                'pwd'=>password_hash(input('post.pwd'),PASSWORD_BCRYPT),
                'login_ip'=>request()->ip(),
                'status'=>input('post.status'),
                'create_time'=>time(),
            ];
            $uid =  Db::name('users')->insertGetId($data);
            if($uid){
                $data = [
                    'uid'=>$uid,
                    'role_id'=>$role_id
                ];
                $id =  Db::name('users_role')->insertGetId($data);
                if($id)
                {
                    echo 'true';
                    exit;
                }else{
                    echo 'false';
                    exit;
                }
            }else{
                echo 'false';
                exit;
            }
        }else{
            //获取所有角色
            $role = Db::name('auth_role')->field('id,title')->order('id','asc')->where('status',1)->select();
            return view('add',['role'=>$role]);
        }
       
    }

           这样以来我们根据用户登录以后session中储存的uid判断当前登录用户的身份信息,根据获取到的uid查询用户-角色关联表查询到用户的角色id, 然后到角色表获取到该用户可操作的权限菜单。

    封装中间控制器Common.php

<?php
namespace appdmincontroller;
use appBaseController;
use thinkacadeSession;
use libAuth;/**权限认证类**/
class Common extends BaseController
{
    public function initialize(){
        $sess_auth  = session('uid');
        $uname = session('uname');
      
        //判断用户是否登录
        if(!$sess_auth){
            jumpTo('/login/index');
            exit;
            //检查到用户登录后, 还要检测该用户是否具有操作某个页面的权限, (是否具有操作某个方法的权限)
        }else{
            $auth = new Auth();
            if(!$auth->check(request()->controller().'/'.request()->action(),$sess_auth)){
                historyTo('抱歉~你没有操作该栏目的权限,请联系管理员!');
                exit;
            }
        }
    }
}

    libAuth;/**权限认证类**/
    

<?php
use thinkacadeDb;
use thinkacadeConfig;
use thinkacadeSession;
use thinkacadeRequest;



class Auth
{
    protected $_config = [
        'auth_on'           =>  true,                // 认证开关
        'auth_type'         =>  1,                   // 认证方式,1为实时认证;2为登录认证。
        'auth_role'        =>  'auth_role',        // 用户组数据表名
        'users_role' =>   'users_role', // 用户-用户组关系表
        'auth_rule'         =>  'auth_rule',         // 权限规则表
        'auth_user'         =>  'users',             // 用户信息表
        
    ];
    
    public function __construct()
    {
        if (Config::get('app.auth')) {
            $this->_config = array_merge($this->_config, Config::get('app.auth'));
        }
    }
    
    /**
     * 检查权限
     * @param  string|array  $name     需要验证的规则列表,支持逗号分隔的权限规则或索引数组
     * @param  integer  $uid      认证用户ID
     * @param  string   $relation 如果为 'or' 表示满足任一条规则即通过验证;如果为 'and' 则表示需满足所有规则才能通过验证
     * @param  string   $mode     执行check的模式
     * @param  integer  $type     规则类型
     * @return boolean           通过验证返回true;失败返回false
     */
    public function check($name, $uid, $relation = 'or', $mode = 'url', $type = 1)
    {
        if (!$this->_config['auth_on']) {
            return true;
        }
        $authList = $this->getAuthList($uid, $type);
        if (is_string($name)) {
            $name = strtolower($name);
            if (strpos($name, ',') !== false) {
                $name = explode(',', $name);
            } else {
                $name = [$name];
            }
        }
        $list = [];
        if ($mode === 'url') {
            $REQUEST = unserialize(strtolower(serialize($_REQUEST)));
        }
        foreach ($authList as $auth) {

            $query = preg_replace('/^.+?/U', '', $auth);
            if ($mode === 'url' && $query != $auth) {
                parse_str($query, $param); // 解析规则中的param
                $intersect = array_intersect_assoc($REQUEST, $param);
                $auth = preg_replace('/?.*$/U', '', $auth);
                if (in_array($auth, $name) && $intersect == $param) {
                    $list[] = $auth;
                }
            } elseif (in_array($auth, $name)) {
                $list[] = $auth;
            }
        }
        if ($relation === 'or' && !empty($list)) {
            return true;
        }
        $diff = array_diff($name, $list);
        if ($relation === 'and' && empty($diff)) {
            return true;
        }
        return false;
    }
    /**
     * 根据用户ID获取用户组,返回值为数组
     * @param  integer $uid 用户ID
     * @return array      用户所属用户组 ['uid'=>'用户ID', 'group_id'=>'用户组ID', 'title'=>'用户组名', 'rules'=>'用户组拥有的规则ID,多个用英文,隔开']
     */
    public function getGroups($uid)
    {
        static $groups = [];
        if (isset($groups[$uid])) {
            return $groups[$uid];
        }
        $user_groups = Db::name($this->_config['users_role'])
            ->alias('ur')
            ->where('ur.uid', $uid)
            ->where('ar.status', 1)
            ->join($this->_config['auth_role'].' ar', "ur.role_id = ar.id")
            ->field('uid,role_id,title,rules')
            ->select();
        $groups[$uid] = $user_groups ?: [];
        return $groups[$uid];
    }
    /**
     * 获得权限列表
     * @param  integer $uid  用户ID
     * @param  integer $type 规则类型
     * @return array       权限列表
     */
    protected function getAuthList($uid, $type)
    {
        static $_authList = [];
        $t = implode(',', (array)$type);
        if (isset($_authList[$uid.$t])) {
            return $_authList[$uid.$t];
        }
        if ($this->_config['auth_type'] == 2 && Session::has('_AUTH_LIST_'.$uid.$t)) {
            return Session::get('_AUTH_LIST_'.$uid.$t);
        }
        // 读取用户所属用户组
        $groups = $this->getGroups($uid);
        $ids = []; // 保存用户所属用户组设置的所有权限规则ID
        foreach ($groups as $g) {
            $ids = array_merge($ids, explode(',', trim($g['rules'], ',')));
        }
        $ids = array_unique($ids);
        if (empty($ids)) {
            $_authList[$uid.$t] = [];
            return [];
        }
        $map = [
            ['id', 'in', $ids],
            ['type', '=', $type],
            ['status', '=', 1]
        ];
        // 读取用户组所有权限规则
        $rules = Db::name($this->_config['auth_rule'])->where($map)->field('condition,name')->select();
        // 循环规则,判断结果。
        $authList = [];
        foreach ($rules as $rule) {
            if (!empty($rule['condition'])) { // 根据condition进行验证
                $user = $this->getUserInfo($uid); // 获取用户信息,一维数组
                $command = preg_replace('/{(w*?)}/', '$user['\1']', $rule['condition']);
                // dump($command); // debug
                @(eval('$condition=('.$command.');'));
                if ($condition) {
                    $authList[] = strtolower($rule['name']);
                }
            } else {
                // 只要存在就记录
                $authList[] = strtolower($rule['name']);
            }
        }
        $_authList[$uid.$t] = $authList;
        if ($this->_config['auth_type'] == 2) {
            Session::set('_AUTH_LIST_'.$uid.$t, $authList);
        }
        return array_unique($authList);
    }
    /**
     * 获得用户资料,根据自己的情况读取数据库
     */
    protected function getUserInfo($uid) {
        static $user_info = [];

        $user = Db::name($this->config['auth_user']);
        // 获取用户表主键
        $_pk = is_string($user->getPk()) ? $user->getPk() : 'uid';
        if (!isset($user_info[$uid])) {
            $user_info[$uid] = $user->where($_pk, $uid)->find();
        }

        return $user_info[$uid];
    }
}

    这样就能实现路由操作权限的实时检测,比如我们让首页控制器继承中间控制器:

<?php
namespace appdmincontroller;
use thinkRequest;
use thinkacadeDb;//db类
use thinkacadeSession;
use libRule;
class Index extends Common
{
    public function index()
    { 
        $uname = session('uname');
        $uid = session('uid');
        // 根据uid,获取该用户相应的权限,连表查询
        $res = Db::name('users')->alias('u')->where('u.uid',$uid)
        ->leftJoin('users_role ur','ur.uid = u.uid')
        ->leftJoin('auth_role ar','ar.id = ur.role_id')
        ->field('u.uid,u.uname,ar.rules')
        ->select()->toArray();
        // dd($res);
        $rules = implode(",",array_column($res,'rules'));
        // dd( $rules);
        //in查询 根据获取到的rules id 选权限列表
        $res = Db::name('auth_rule')->field('id,name,title,pid')->order('id','asc')->where('is_menu',1)
        ->where('id','in',$rules)->select();
        // dump($res);
        //这里使用扩展类Rule中封装的无限极分类方法
        $rlist = Rule::Rulelayer($res);
        // dd($rlist);
        $data = [
            'uid'=>$uid,
            'uname'=>$uname,
            'rlist'=>$rlist,
            'create_time'=>1617252175
        ];
            return view('index', $data);
            
    }
 }

最终实现的效果如图:

11.png


热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
Golang 测试体系与代码质量保障:工程级可靠性建设
Golang 测试体系与代码质量保障:工程级可靠性建设

Go语言测试体系与代码质量保障聚焦于构建工程级可靠性系统。本专题深入解析Go的测试工具链(如go test)、单元测试、集成测试及端到端测试实践,结合代码覆盖率分析、静态代码扫描(如go vet)和动态分析工具,建立全链路质量监控机制。通过自动化测试框架、持续集成(CI)流水线配置及代码审查规范,实现测试用例管理、缺陷追踪与质量门禁控制,确保代码健壮性与可维护性,为高可靠性工程系统提供质量保障。

48

2026.02.28

Golang 工程化架构设计:可维护与可演进系统构建
Golang 工程化架构设计:可维护与可演进系统构建

Go语言工程化架构设计专注于构建高可维护性、可演进的企业级系统。本专题深入探讨Go项目的目录结构设计、模块划分、依赖管理等核心架构原则,涵盖微服务架构、领域驱动设计(DDD)在Go中的实践应用。通过实战案例解析接口抽象、错误处理、配置管理、日志监控等关键工程化技术,帮助开发者掌握构建稳定、可扩展Go应用的最佳实践方法。

44

2026.02.28

Golang 性能分析与运行时机制:构建高性能程序
Golang 性能分析与运行时机制:构建高性能程序

Go语言以其高效的并发模型和优异的性能表现广泛应用于高并发、高性能场景。其运行时机制包括 Goroutine 调度、内存管理、垃圾回收等方面,深入理解这些机制有助于编写更高效稳定的程序。本专题将系统讲解 Golang 的性能分析工具使用、常见性能瓶颈定位及优化策略,并结合实际案例剖析 Go 程序的运行时行为,帮助开发者掌握构建高性能应用的关键技能。

37

2026.02.28

Golang 并发编程模型与工程实践:从语言特性到系统性能
Golang 并发编程模型与工程实践:从语言特性到系统性能

本专题系统讲解 Golang 并发编程模型,从语言级特性出发,深入理解 goroutine、channel 与调度机制。结合工程实践,分析并发设计模式、性能瓶颈与资源控制策略,帮助将并发能力有效转化为稳定、可扩展的系统性能优势。

22

2026.02.27

Golang 高级特性与最佳实践:提升代码艺术
Golang 高级特性与最佳实践:提升代码艺术

本专题深入剖析 Golang 的高级特性与工程级最佳实践,涵盖并发模型、内存管理、接口设计与错误处理策略。通过真实场景与代码对比,引导从“可运行”走向“高质量”,帮助构建高性能、可扩展、易维护的优雅 Go 代码体系。

19

2026.02.27

Golang 测试与调试专题:确保代码可靠性
Golang 测试与调试专题:确保代码可靠性

本专题聚焦 Golang 的测试与调试体系,系统讲解单元测试、表驱动测试、基准测试与覆盖率分析方法,并深入剖析调试工具与常见问题定位思路。通过实践示例,引导建立可验证、可回归的工程习惯,从而持续提升代码可靠性与可维护性。

3

2026.02.27

漫蛙app官网链接入口
漫蛙app官网链接入口

漫蛙App官网提供多条稳定入口,包括 https://manwa.me、https

268

2026.02.27

deepseek在线提问
deepseek在线提问

本合集汇总了DeepSeek在线提问技巧与免登录使用入口,助你快速上手AI对话、写作、分析等功能。阅读专题下面的文章了解更多详细内容。

51

2026.02.27

AO3官网直接进入
AO3官网直接进入

AO3官网最新入口合集,汇总2026年可用官方及镜像链接,助你快速稳定访问Archive of Our Own平台。阅读专题下面的文章了解更多详细内容。

430

2026.02.27

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

相关下载

更多

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Git 教程
Git 教程

共21课时 | 4万人学习

PHP自制框架
PHP自制框架

共8课时 | 0.6万人学习

ThinkPHP6.x 微实战--十天技能课堂
ThinkPHP6.x 微实战--十天技能课堂

共26课时 | 1.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号