如何使用RBAC思想進行資料表的設計
如果我們的專案允許一個後臺管理使用者可能有1個或者2個及2個以上的多個角色,按照下面進行設計:
許可權選單表,角色表,使用者表是互相獨立的。設計表的順序是許可權選單表,角色表,使用者表,使用者-角色關聯表。
1. 首先是許可權選單表設計如下:
注意:許可權選單可以是多級選單,新增pid欄位,方便無限極遞迴分類。
2. 角色表設計如下:
3. 使用者表設計如下:
4. 最後是使用者-角色關聯表設計如下:
當超級管理員在後臺需要新增新使用者時,不僅需要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 app\admin\controller; use app\BaseController; use think\facade\Session; use lib\Auth;/**許可權認證類**/ 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; } } } }
lib\Auth;/**許可權認證類**/
<?php use think\facade\Db; use think\facade\Config; use think\facade\Session; use think\facade\Request; 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 app\admin\controller; use think\Request; use think\facade\Db;//db類 use think\facade\Session; use lib\Rule; 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); } }
最終實現的效果如圖:
以上就是RBAC許可權控制實現原理——許可權表、使用者表與關聯表設計的詳細內容,更多請關注TW511.COM其它相關文章!