PS:onethink是基於該權限認證類實現,Auth類作為官方類庫,在Library\Think里面。
其實Auth類也是基於角色訪問控制RBAC擴展的,具體到節點的權限校驗方式還是需要根據業務需求擴展。
Auth和RBAC
Rbac:
- Rbac是基於節點控制,根據3級節點,module,controller,action,節點類似與樹形結構,3級節點間相互有關聯
- 表關系:
用戶表->用戶角色關聯表->角色表->角色節點關聯表->節點表
- 根據3級節點控制,粒度到操作action,每個節點為單一的模塊,控制器或操作
Auth:
- 是對規則進行認證,不是對節點進行認證。用戶可以把節點當作規則名稱實現對節點進行認證。
- 表關系:
用戶表->用戶和用戶組關聯表->用戶組表->規則表
- 根據規則控制,可自由定制不同的規則,非常自由,同一個規則內可以定制多個不同節點(中間的關系:OR AND)
- 可定制規則表達式,比如定制積分表達式
- 一個用戶可以屬於多個用戶組。我們需要設置每個用戶組擁有哪些規則
RBAC是按節點進行認證的,如果要控制比節點更細的權限就有點困難了,比如頁面上面的操作按鈕, 我想判斷用戶權限來顯示這個按鈕, 如果沒有權限就不會顯示這個按鈕; 再比如我想按積分進行權限認證, 積分在0-100時能干什么, 在101-200時能干什么。 這些權限認證用RABC都很困難。
而Auth權限認證, 它幾乎是全能的, 除了能進行節點認證, 上面說的RABC很難認證的兩種情況,它都能實現。
Auth的認證過程
數據庫表介紹
用戶表自行設計喲,提供uid即可
1 //數據庫 2 /* 3 -- ---------------------------- 4 -- think_auth_rule,規則表, 5 -- id:主鍵,name:規則唯一標識, title:規則中文名稱 status 狀態:為1啟用,為0禁用,condition:規則表達式,為空表示存在就驗證,不為空表示按照條件驗證 6 -- ---------------------------- 7 DROP TABLE IF EXISTS `think_auth_rule`; 8 CREATE TABLE `think_auth_rule` ( 9 `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT, 10 `name` char(80) NOT NULL DEFAULT '', 11 `title` char(20) NOT NULL DEFAULT '', 12 `type` tinyint(1) NOT NULL DEFAULT '1', #1-url,2-主菜單,這個字段可以根據業務邏輯,自己定義類別,見getAuthList函數 13 `status` tinyint(1) NOT NULL DEFAULT '1', 14 `condition` char(100) NOT NULL DEFAULT '', # 規則附件條件,滿足附加條件的規則,才認為是有效的規則 15 PRIMARY KEY (`id`), 16 UNIQUE KEY `name` (`name`) 17 ) ENGINE=MyISAM DEFAULT CHARSET=utf8; 18 -- ---------------------------- 19 -- think_auth_group 用戶組表, 20 -- id:主鍵, title:用戶組中文名稱, rules:用戶組擁有的規則id, 多個規則","隔開,status 狀態:為1啟用,為0禁用 21 -- ---------------------------- 22 DROP TABLE IF EXISTS `think_auth_group`; 23 CREATE TABLE `think_auth_group` ( 24 `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT, 25 `title` char(100) NOT NULL DEFAULT '', 26 `status` tinyint(1) NOT NULL DEFAULT '1',#用戶組狀態:為1正常,為0禁用,-1為刪除 27 `rules` char(80) NOT NULL DEFAULT '', #rule表的ID值,用逗號分隔,這樣這個用戶組就可以用多種規則了。 28 PRIMARY KEY (`id`) 29 ) ENGINE=MyISAM DEFAULT CHARSET=utf8; 30 -- ---------------------------- 31 -- think_auth_group_access 用戶組明細表 32 -- uid:用戶id,group_id:用戶組id 33 -- ---------------------------- 34 DROP TABLE IF EXISTS `think_auth_group_access`; 35 CREATE TABLE `think_auth_group_access` ( 36 `uid` mediumint(8) unsigned NOT NULL, 37 `group_id` mediumint(8) unsigned NOT NULL, 38 UNIQUE KEY `uid_group_id` (`uid`,`group_id`), 39 KEY `uid` (`uid`), 40 KEY `group_id` (`group_id`) 41 ) ENGINE=MyISAM DEFAULT CHARSET=utf8; 42 */
Thinkphp中Auth源碼
僅僅160行~
1 class Auth{ 2 3 //默認配置 4 protected $_config = array( 5 'AUTH_ON' => true, // 認證開關 6 'AUTH_TYPE' => 1, // 認證方式,1為實時認證;2為登錄認證。 7 'AUTH_GROUP' => 'auth_group', // 用戶組數據表名 8 'AUTH_GROUP_ACCESS' => 'auth_group_access', // 用戶-用戶組關系表 9 'AUTH_RULE' => 'auth_rule', // 權限規則表 10 'AUTH_USER' => 'member' // 用戶信息表 11 ); 12 13 public function __construct() { 14 $prefix = C('DB_PREFIX'); 15 $this->_config['AUTH_GROUP'] = $prefix.$this->_config['AUTH_GROUP']; 16 $this->_config['AUTH_RULE'] = $prefix.$this->_config['AUTH_RULE']; 17 $this->_config['AUTH_USER'] = $prefix.$this->_config['AUTH_USER']; 18 $this->_config['AUTH_GROUP_ACCESS'] = $prefix.$this->_config['AUTH_GROUP_ACCESS']; 19 if (C('AUTH_CONFIG')) { 20 //可設置配置項 AUTH_CONFIG, 此配置項為數組。 21 $this->_config = array_merge($this->_config, C('AUTH_CONFIG')); 22 } 23 } 24 25 /** 26 * 檢查權限 27 * @param name string|array 需要驗證的規則列表,支持逗號分隔的權限規則或索引數組 28 * @param uid int 認證用戶的id 29 * @param string mode 執行check的模式 30 * @param relation string 如果為 'or' 表示滿足任一條規則即通過驗證;如果為 'and'則表示需滿足所有規則才能通過驗證 31 * @return boolean 通過驗證返回true;失敗返回false 32 */ 33 public function check($name, $uid, $type=1, $mode='url', $relation='or') { 34 if (!$this->_config['AUTH_ON']) 35 return true; 36 //獲取用戶需要驗證的所有有效規則列表 37 $authList = $this->getAuthList($uid,$type); 38 //是否驗證多個規則,包含逗號,變成數組 39 if (is_string($name)) { 40 $name = strtolower($name); 41 if (strpos($name, ',') !== false) { 42 $name = explode(',', $name); 43 } else { 44 $name = array($name); 45 } 46 } 47 $list = array(); //保存驗證通過的規則名 48 if ($mode=='url') { 49 //序列化,小寫,又反序列化? 50 $REQUEST = unserialize( strtolower(serialize($_REQUEST)) ); 51 } 52 foreach ( $authList as $auth ) { 53 //獲取url的query參數 54 $query = preg_replace('/^.+\?/U','',$auth); 55 if ($mode=='url' && $query!=$auth ) { 56 //解析規則中的param,使其變成PHP變量 57 parse_str($query,$param); 58 //獲取$REQUEST與$param帶索引檢查計算數組的交集 59 $intersect = array_intersect_assoc($REQUEST,$param); 60 //獲取auth中的的query參數 61 $auth = preg_replace('/\?.*$/U','',$auth); 62 if ( in_array($auth,$name) && $intersect==$param ) { //如果節點相符且url參數滿足 63 $list[] = $auth ; 64 } 65 }else if (in_array($auth , $name)){ 66 //例如,$name='Admin/index',不帶query參數,就不是url模式,那么就直接判斷是否包含,否則如上代碼判斷url參數是否一致。 67 $list[] = $auth ; 68 } 69 } 70 if ($relation == 'or' and !empty($list)) { 71 return true; 72 } 73 $diff = array_diff($name, $list); 74 if ($relation == 'and' and empty($diff)) { 75 return true; 76 } 77 return false; 78 } 79 80 /** 81 * 根據用戶id獲取用戶組,返回值為數組 82 * @param uid int 用戶id 83 * @return array 用戶所屬的用戶組 array( 84 * array('uid'=>'用戶id','group_id'=>'用戶組id','title'=>'用戶組名稱','rules'=>'用戶組擁有的規則id,多個,號隔開'), 85 * ...) 86 */ 87 public function getGroups($uid) { 88 static $groups = array(); 89 if (isset($groups[$uid])) 90 return $groups[$uid]; 91 $user_groups = M() 92 ->table($this->_config['AUTH_GROUP_ACCESS'] . ' a') 93 ->where("a.uid='$uid' and g.status='1'") 94 ->join($this->_config['AUTH_GROUP']." g on a.group_id=g.id") 95 ->field('uid,group_id,title,rules')->select(); 96 $groups[$uid]=$user_groups?:array(); 97 return $groups[$uid]; 98 } 99 100 /** 101 * 獲得權限列表 102 * @param integer $uid 用戶id 103 * @param integer $type 104 */ 105 protected function getAuthList($uid,$type) { 106 static $_authList = array(); //保存用戶驗證通過的權限列表 107 $t = implode(',',(array)$type); 108 if (isset($_authList[$uid.$t])) { 109 return $_authList[$uid.$t]; 110 } 111 if( $this->_config['AUTH_TYPE']==2 && isset($_SESSION['_AUTH_LIST_'.$uid.$t])){ 112 return $_SESSION['_AUTH_LIST_'.$uid.$t]; 113 } 114 115 //讀取用戶所屬用戶組,一個用戶可以屬於多個用戶組 116 $groups = $this->getGroups($uid); 117 $ids = array();//保存用戶所屬用戶組設置的所有權限規則id 118 foreach ($groups as $g) { 119 $ids = array_merge($ids, explode(',', trim($g['rules'], ','))); 120 } 121 $ids = array_unique($ids); 122 if (empty($ids)) { 123 $_authList[$uid.$t] = array(); 124 return array(); 125 } 126 127 $map=array( 128 'id'=>array('in',$ids), 129 'type'=>$type, 130 'status'=>1, 131 ); 132 //讀取用戶組所有權限規則 133 $rules = M()->table($this->_config['AUTH_RULE'])->where($map)->field('condition,name')->select(); 134 135 //循環規則,判斷結果。 136 $authList = array(); // 137 foreach ($rules as $rule) { 138 if (!empty($rule['condition'])) { //根據condition進行驗證 139 $user = $this->getUserInfo($uid);//獲取用戶信息,一維數組 140 //使用用戶的信息進行條件判斷 141 //正則表達式替換,如定義{score}>5 and {score}<100 表示用戶的分數在5-100之間時這條規則才會通過。 142 $command = preg_replace('/\{(\w*?)\}/', '$user[\'\\1\']', $rule['condition']); 143 //eval() 函數把字符串按照 PHP 代碼來計算。 144 @(eval('$condition=(' . $command . ');')); 145 if ($condition) { 146 $authList[] = strtolower($rule['name']); 147 } 148 } else { 149 //只要存在就記錄 150 $authList[] = strtolower($rule['name']); 151 } 152 } 153 $_authList[$uid.$t] = $authList; 154 if($this->_config['AUTH_TYPE']==2){ 155 //如果是登錄認證,規則列表結果保存到session,不是實時認證,就可以直接用session緩存了 156 $_SESSION['_AUTH_LIST_'.$uid.$t]=$authList; 157 } 158 return array_unique($authList); 159 } 160 161 /** 162 * 獲得用戶資料,根據自己的情況讀取數據庫 163 */ 164 protected function getUserInfo($uid) { 165 static $userinfo=array(); 166 if(!isset($userinfo[$uid])){ 167 $userinfo[$uid]=M()->where(array('uid'=>$uid))->table($this->_config['AUTH_USER'])->find(); 168 } 169 return $userinfo[$uid]; 170 } 171 172 }
實際使用
非常簡單,就一個函數搞定
public function check($name, $uid, $type=1, $mode='url', $relation='or')
/** * 權限檢測 * @param string $rule 檢測的規則,例如Admin/Category/add,Admin/Category/edit,Admin/User/changeStatus?method=deleteUser * @param string $mode check模式 * @return boolean */ final protected function checkRule($rule, $type=AuthRuleModel::RULE_URL, $mode='url'){ if(IS_ROOT){ return true;//管理員允許訪問任何頁面 } static $Auth = null; if (!$Auth) { $Auth = new \Think\Auth(); } //$rule = strtolower(MODULE_NAME.'/'.CONTROLLER_NAME.'/'.ACTION_NAME); if(!$Auth->check($rule,UID,$type,$mode)){ return false; } return true; }
參考
http://www.thinkphp.cn/topic/4029.html
http://www.thinkphp.cn/extend/675.html