權(quán)限控制
權(quán)限控制顧名思義就是控制什么“人”能(不能)訪問(wèn)什么(“操作”)。在身份認(rèn)證中,我們已經(jīng)知道了人是誰(shuí),現(xiàn)在這里就介紹yangzie如何控制這些人能做什么,yangzie是基于ACL來(lái)實(shí)現(xiàn)授權(quán)功能的,那么這里需要先闡明幾個(gè)概念:

Access Control Object
ACO訪問(wèn)控制對(duì)象,也就是ARO要請(qǐng)求訪問(wèn)的對(duì)象,在yangzie中也就是M/C/A(見(jiàn)yangzie基本原則),每個(gè)具體的ACO也有一個(gè)標(biāo)識(shí),就按照M/C/A的格式進(jìn)行標(biāo)識(shí),比如order模塊下面的增加訂單add,假如他都組織在index控制器下(index_controller),action為add;那么該ACO的標(biāo)識(shí)就是:/order/index/add;
根據(jù)具體的業(yè)務(wù)要求,ACO可以定義到某個(gè)具體的Action級(jí)別,或者Controller級(jí)別或者整個(gè)module級(jí)別
Access Request Object
ARO訪問(wèn)請(qǐng)求對(duì)象,也就是要請(qǐng)求ACO的對(duì)象,通常他指“人”,各種各樣的類型的人,每種“類型”有一個(gè)唯一的標(biāo)識(shí),用于區(qū)別一類人,比如某種角色的,也可以唯一標(biāo)識(shí)一個(gè)人;
這個(gè)標(biāo)識(shí)yangzie的設(shè)計(jì)是從大類/小類/具體某個(gè)用戶id然后以/進(jìn)行分割,舉個(gè)例子,假如系統(tǒng)中存在這這幾種角色,超級(jí)管理員,普通管理員,銷售;可能這幾種角色的ARO標(biāo)識(shí)看起來(lái)是這個(gè)樣子:
- 超級(jí)管理員:/admin/root
- 普通管理員:/admin/normal
- 銷售:/salesman
- 管理員A:/admin/normal/12344
為什么要這樣設(shè)計(jì),是因?yàn)樵谶M(jìn)行授權(quán)時(shí)可以把權(quán)限分配給組或者具體某個(gè)類型或者具體某個(gè)人;然后具體的某個(gè)用戶,如上面的管理員A,在驗(yàn)證權(quán)限時(shí),再?gòu)木唧wid到大類來(lái)逐一驗(yàn)證,直到找到具體的權(quán)限是允許還是拒絕為止。
ACL
ARO和ACO分別定義了“操作”和“人”,那么剩下的就是把這兩個(gè)關(guān)聯(lián)起來(lái),從而達(dá)到什么人能訪問(wèn)什么的目的;這個(gè)關(guān)聯(lián)的地方就是AccessControlList;在yangzie中它是一個(gè)配置文件,位于app/__aros_acos__.php;默認(rèn)的內(nèi)容如下:
function yze_get_aco_desc($aconame) {
foreach ( ( array ) yze_get_acos_aros () as $aco => $desc ) {
if (preg_match ( "{^" . $aco . "}", $aconame )) {
return @$desc ['desc'];
}
}
return '';
}
function yze_get_ignore_acos() {
return array();
}
function yze_get_acos_aros() {
$array = array (
"/" => array (//module/controller/action
"deny" => "",
"allow" => array (
"*" //aro
),
"desc" => ""http://功能說(shuō)明
)
);
return $array;
}
該文件主要包含3個(gè)函數(shù):
- yze_get_acos_aros:該方法返回ACL列表
- yze_get_ignore_acos:該方法返回忽略權(quán)限控制的ACO,當(dāng)ARO請(qǐng)求這些內(nèi)容時(shí)忽略權(quán)限控制
- yze_get_aco_desc: 助手方法,返回ACO功能的描述,
ACL的格式
ACL是一個(gè)數(shù)組,格式如下:
array (
"ACO1 name" => array (
"deny" => "",//拒絕的ARO
"allow" => array (
"*" //允許的ARO
),
"desc" => ""http://ACO功能說(shuō)明
),
"ACO2 name" => array (
"deny" => "",//拒絕的ARO
"allow" => "*", //允許的ARO,
"desc" => ""http://ACO功能說(shuō)明
),
);
ACO name是ACO的標(biāo)識(shí),根據(jù)權(quán)限控制的級(jí)別可以具體定義到Action級(jí)別,或者只定義到Module級(jí)別;
deny定義黑名單,拒絕里面列出的ARO的訪問(wèn);allow定義白名單,允許里面列出的ARO
ACL定義規(guī)則
- ACO和ARO都可以使用正則表達(dá)
- 黑名單優(yōu)先級(jí)大于白名單
- 兩者都可以采用”*”來(lái)代表所有
- 如果要指定具體的aro,deny和allow中以數(shù)組的形式列出ARO
一個(gè)真實(shí)的例子:
function yze_get_ignore_acos() {
return array(
"/card/front",
"/package/package/grab",
"/package/package/grabfail",
"/package/front",
"/zb/middlepage/view",
"/wxbbs/front",
"/zbad/front",
);
}
function yze_get_acos_aros() {
$array = array (
"/admin" => array (
"deny" => "*",
"allow" => array (
"/admin"
),
"desc" => "后端管理"
),
"/sp/consumers" => array (
"deny" => "*",
"allow" => array (
"/sp"
),
"desc" => "粉絲管理"
)
);
if (YDWXP_TYPE == YDWXP_TYPE_MARKET || YDWXP_TYPE == YDWXP_TYPE_ALL) {
$array ['/card'] = array (
"deny" => "*",
"allow" => array ("/sp"),
"desc" => "卡券管理"
);
$array ['/card/card/*consume'] = array (
"deny" => "*",
"allow" => array ("/sp"),
"desc" => "卡券核銷"
);
$array ['/card/merchants'] = array (
"deny" => "*",
"allow" => array ("/sp/super","/sp/common"),
"desc" => "商戶管理"
);
}
return $array;
}
ARO怎么定義
ACO是根據(jù)M/C/A來(lái)定義的,這很明確和具體,但ARO標(biāo)識(shí)怎么根據(jù)用戶來(lái)定義?這也是通過(guò)hook來(lái)實(shí)現(xiàn)的,在app/hooks/auth.php中已經(jīng)定義注冊(cè)了該hook,開(kāi)發(fā)者只需要實(shí)現(xiàn)即可,這里有一個(gè)真實(shí)的例子:
YZE_Hook::add_hook ( YZE_FILTER_GET_USER_ARO_NAME, function ( $data ) {
if ( !@$_SESSION [ 'admin' ] )return "/";
if(is_a($_SESSION['admin'], "\\app\\sp\\Consumer_Model")){
return "/consumer";
}
if($_SESSION [ 'admin' ]->sp_id) {
//是否子商戶
if ( ! isset($_SESSION [ 'is_sub_merchant' ]) ) {
$_SESSION [ 'is_sub_merchant' ] = YZE_Hook::do_hook(YDMARKET_IS_SUB_MERCHANT, $_SESSION [ 'admin' ]->sp_id);
}
if ($_SESSION [ 'is_sub_merchant' ]) {
return "/".$_SESSION [ 'admin' ]->user->type."/sub/".$_SESSION [ 'admin' ]->type;
}
}
return "/".$_SESSION [ 'admin' ]->user->type."/".$_SESSION [ 'admin' ]->type;
} );
根據(jù)自己的業(yè)務(wù)系統(tǒng)的用戶類型,自行設(shè)計(jì)自己的ARO標(biāo)識(shí)接口。
能靈活的配置權(quán)限嗎?
ACL列表屬于事先硬編碼的控制列表,但在實(shí)際的業(yè)務(wù)環(huán)節(jié)中很多權(quán)限都是需要?jiǎng)討B(tài)分配的,比如給某中角色設(shè)置權(quán)限,給具體某個(gè)人設(shè)置權(quán)限;這在yangzie中如何做呢?
yangzie有兩個(gè)預(yù)留函數(shù)get_permissions($aroname),get_user_permissions();
這兩個(gè)函數(shù)由開(kāi)發(fā)者實(shí)現(xiàn),分別返回指定aroname的acl或者返回某個(gè)具體用戶的acl,這通常是具體的業(yè)務(wù)要求決定的,返回的仍然是acl數(shù)組結(jié)構(gòu)。這兩個(gè)函數(shù)放在任意的自動(dòng)包含文件中(見(jiàn)__config__.php說(shuō)明),但要注意
這兩個(gè)函數(shù)必須屬于頂級(jí)命名空間
當(dāng)有這兩個(gè)函數(shù)的實(shí)現(xiàn)時(shí),他們的返回結(jié)果將覆蓋ACL里面的控制
ACL的效果是什么
當(dāng)配置好ACL后,如果一個(gè)用戶訪問(wèn)被deny的內(nèi)容,如某個(gè)action,某個(gè)controller或者某個(gè)module;yangzie將會(huì)拋出YZE_Permission_Deny_Exception異常,開(kāi)發(fā)者可以通過(guò)hook YZE_FILTER_YZE_EXCEPTION來(lái)監(jiān)聽(tīng)然后做響應(yīng)的處理,默認(rèn)情況下將顯示500頁(yè)面;
這就是說(shuō)yangzie會(huì)根據(jù)ACL來(lái)驗(yàn)證用戶的權(quán)限,如果用戶沒(méi)有權(quán)限就會(huì)立即拋出異常,這在進(jìn)入開(kāi)發(fā)者的業(yè)務(wù)邏輯之前就已經(jīng)處理,開(kāi)發(fā)者無(wú)需在自己的功能代碼中去驗(yàn)證當(dāng)前用戶是誰(shuí),是否有權(quán)限方法;開(kāi)發(fā)者就只關(guān)注自己的業(yè)務(wù)邏輯即可。
GET和POST的特殊情況
yangzie的Action包含兩種情況,get方式和post方法,比如一個(gè)增加用戶的例子,假如M/C/A是:users/index/add;那么他對(duì)應(yīng)的post action就是users/index/post_add,會(huì)多一個(gè)post前綴,如果在ACL需要針對(duì)這兩種情況單獨(dú)控制,比如允許get但不允許post,也就是允許看,但不允許提交,那在ACL中需要分配針對(duì)兩個(gè)action(add,post_add)進(jìn)行處理
如何通過(guò)ACL來(lái)控制輸出
在實(shí)際的業(yè)務(wù)系統(tǒng)中,通常需要根據(jù)權(quán)限來(lái)控制輸出的響應(yīng)中某些部分用戶可見(jiàn),某些部分用戶不可見(jiàn),那么這可以通過(guò)兩種方式來(lái)實(shí)現(xiàn)。
方法一:通過(guò)YZE_ACL::get_instance()->check_byname($aroname, $aconame)來(lái)判斷并輸出
if(YZE_ACL::get_instance()->check_byname($aroname, $aconame)){
// your output html
}
方法二:通過(guò)YZE_ACL::get_instance()->begin_check_permission($id, $aroname, $aconame)….YZE_ACL::get_instance()->end_check_permission($id, $aroname, $aconame)來(lái)輸出
YZE_ACL::get_instance()->begin_check_permission($id, $aroname, $aconame)
// your output html
YZE_ACL::get_instance()->end_check_permission($id, $aroname, $aconame)
根據(jù)你的習(xí)慣來(lái)選擇,如果你不喜歡跨度很大的if(){},那用第二種方式是個(gè)好選擇
