- config.php配置INJECT_RBAC
// key为标识字段,用户登录后存入session中
// user为RBAC对应的用户数据表(程序使用D函数)
// role_user为用户与用户组关联数据表(程序使用原生sql)
'INJECT_RBAC' => [
['key' => 'PUBLIC_USER_LOGIN_ID', 'user' => 'User', 'role_user' => 'qs_role_user']
]
-
用户登录后使用cleanRbacKey清空key的session值再重置对应key的session值
-
用户登出后使用cleanRbacKey清空key的session值
不同用户一般只能看到与自己相关的数据,该机制可以限制后台用户访问数据的权限,不用针对不同用户分别处理where表达式,降低开发难度。
-
用户登录成功后设置AUTH_RULE_ID的session值;
-
配置对应Model类的$_auth_ref_rule(若查询数据表存在别名,自动处理auth_ref_key为“别名.字段名”)
如某机构(全思小伙伴)只能查看其创建书库点的书箱,则:
// auth_ref_key是与用户关联的字段,即AUTH_RULE_ID的值
// ref_path是该字段有关的数据表及其对应字段
// 机构OrganizationModel的配置
protected $_auth_ref_rule = array(
'auth_ref_key' => 'id',
'ref_path' => 'Organization.id'
);
// 书库点LibraryModel的配置
protected $_auth_ref_rule = array(
'auth_ref_key' => 'org_id',
'ref_path' => 'Organization.id'
);
// 书箱BoxModel的配置
protected $_auth_ref_rule = array(
'auth_ref_key' => 'library_id',
'ref_path' => 'Library.id'
);
若不使用该机制,则需要根据登录用户来处理where表达式,会导致查询的层级越高,代码量就越多:
// 不使用该机制,机构查询书箱数据,则需要根据登录用户获取org_id,再根据org_id获取library_id,再根据library_id获取书箱id,最后根据书箱id找出书箱数据:
if (session('?' . C('USER_AUTH_KEY')){
$org_id = D('OrganizationUser')->where(['id' => session(C('USER_AUTH_KEY'))])->getField('org_id');
!$org_id && $org_map['_string'] = "1=0";
$org_id && $library_ids = D('Library')->where(['org_id' => $org_id])->getField('id', true);
if ($library_ids){
$box_ids = D('Box')->where(['library_id'=>['IN',$library_ids]]) -> getField('id',true);
$box_ids && $org_map['id'] = ['IN', $box_ids];
!$box_ids && $org_map['_string'] = "1=0";
}else{
$org_map['_string'] = "1=0";
}
$org_map && $map = array_merge($map, $org_map);
}
D('Box')->where($map)->select();
使用该机制后,机构查询书箱数据:
D('Box')->where($map)->select();
如系统存在机构用户OrgUser与书库点管理员LibraryUser,有书库点Library数据分别与他们关联(org_id与id)时,对应的LibraryModel应该这样配置:
// 对于OrgUser,书库点LibraryModel的配置
protected $_auth_ref_rule = array(
'auth_ref_key' => 'org_id',
'ref_path' => 'Organization.id'
);
// 对于LibraryUser,书库点LibraryModel的配置
protected $_auth_ref_rule = array(
'auth_ref_key' => 'id',
'ref_path' => 'Library.id'
);
显然之前的权限过滤机制不能满足需求。
当系统存在多种不同类型的用户,而这些用户与数据相关联的字段不一致,扩展后可以根据不同类型的用户配置不同的权限过滤。
- 配置对应Model类的$_auth_ref_rule,自定义不同用户类型的权限过滤
// 机构OrganizationModel的配置
protected $_auth_ref_rule = array(
'auth_ref_key' => 'id',
'ref_path' => 'Organization.id'
);
// 用户类型org为机构
// 用户类型library为书库点管理员
// 书库点LibraryModel的配置
protected $_auth_ref_rule = array(
'org' => [
'auth_ref_key' => 'org_id',
'ref_path' => 'Organization.id'
],
'library' => [
'auth_ref_key' => 'id',
'ref_path' => 'Library.id'
]
);
- 登录方法需先清空再设置“AUTH_RULE_ID”、“AUTH_ROLE_TYPE”对应的值
// 用户类型为“library”的登录方法
public function libraryUserLogin($name,$pwd){
// 省略登录逻辑处理
……
……
……
// 登录成功后
if ($r !== false){
cleanRbacKey();
session('LIBRARY_USER_LOGIN_ID',$ent['id']);
session(C('USER_AUTH_KEY'), $ent['id']);
\Qscmf\Core\AuthChain::setAuthFilterKey($ent['library_id'], 'library');
session('ADMIN_LOGIN', true);
session('HOME_LOGIN', null);
sysLogs('书库点管理员后台登录');
return true;
}else{
return false;
}
}
// 用户类型为“org”的登录方法
public function adminLogin($user_name, $pwd){
// 省略登录逻辑处理
……
……
……
// 登录成功后
if (!$ent){
return false;
}
cleanRbacKey();
// 设置超级管理员权限
if ($ent['id'] == C('USER_AUTH_ADMINID')) {
session(C('ADMIN_AUTH_KEY'), true);
} else {
session(C('ADMIN_AUTH_KEY'), false);
}
session('ORG_USER_LOGIN_ID', $ent['id']);
session(C('USER_AUTH_KEY'), $ent['id']);
\Qscmf\Core\AuthChain::setAuthFilterKey($ent['company_id'], 'org');
session('ADMIN_LOGIN', true);
session('HOME_LOGIN', null);
sysLogs($ent['company_id'] ? '机构后台登录' : '平台用户后台登录');
return true;
}
- 登出方法需要使用函数“cleanRbacKey”、“cleanAuthFilterKey”清空对应的值
// 用户类型为“org”的登出方法
public function sso_out(){
if (isAdminLogin()) {
cleanRbacKey();
\Qscmf\Core\AuthChain::cleanAuthFilterKey();
session(C('ADMIN_AUTH_KEY'), null);
session(C('USER_AUTH_KEY'), null);
session('ADMIN_LOGIN', null);
sysLogs('后台登出');
}
}
// 用户类型为“library”的登出方法
public function libraryUserLogout(){
if (isAdminLogin()) {
cleanRbacKey();
\Qscmf\Core\AuthChain::cleanAuthFilterKey();
session(C('ADMIN_AUTH_KEY'), null);
session(C('USER_AUTH_KEY'), null);
session('ADMIN_LOGIN', null);
sysLogs('后台登出');
}
$this->redirect('Public/libraryUserLogin');
}
可以config文件中将 'FRONT_AUTH_FILTER' 设置为true
'FRONT_AUTH_FILTER'=>true,
不存在关联数据时,默认返回空。使用此功能可以实现用户存在关联数据则只能查看关联的数据,若不存在关联数据则可以查看所有数据。
- 配置对应Model类属性$_auth_ref_rule的not_exists_then_ignore,其值为true
该值应设置在需要获取的关联数据主表对应的Model类,如用户(UserModel类)与地区(AreaModel类)关联,需要获取地区的数据,应在AreaModel类设置该值为true。
// 设置该值为true
// AreaModel类的配置
protected $_auth_ref_rule = array(
'auth_ref_key' => 'id',
'ref_path' => 'UserArea.city_id',
'not_exists_then_ignore' => true
);
权限过滤的关联数据为回调函数的结果。
-
定义回调函数
-
配置对应Model类属性$_auth_ref_rule的auth_ref_value_callback
auth_ref_value_callback的值为索引数组。
数组第一个元素为被调用的回调函数,若为公共函数,则为字符串;若为某个类的方法,则为数组,如[类名,方法名];
数组之后的元素是要被传入回调函数的参数。
回调函数接收关联数据的参数位置没有硬性规定,可以根据实际情况使用占位符__auth_ref_value__,程序执行时会根据ref_path的设置,获取关联字段的实际值传给回调函数,注意该值为数组。
该值应设置在需要获取的关联数据主表对应的Model类,如用户(UserModel类)与地区(AreaModel类)关联,需要获取地区的数据,应在AreaModel类设置该值。
// AreaModel类的配置
protected $_auth_ref_rule = array(
'auth_ref_key' => 'id',
'ref_path' => 'UserArea.city_id',
'auth_ref_value_callback' => ['callback_fun_name','__auth_ref_value__','param2'],
);
若用户A与省市区的关联数据为:广东省、湖南省,则该用户可以查看这些省份及其下属所有地区的数据;
若用户B没有关联地区数据,则该用户可以查看所有地区的数据。
// 不使用权限过滤功能,则每次获取用户能查看的地区数据时,都需要过滤地区数据。
public function getArea(){
$user_id = D('User')->getLoginUserId();
$map = [];
D('Area')->genWhereByUid($user_id, $map, 'id');
$area = D('Area')->where($map)->select();
return $area;
}
// AreaModel类
// 根据uid获取可以查看的地区数据
public function genWhereByUid($uid, &$map, $field){
$city_id = D('UserArea')->where(['user_id'=>$uid])->getField('city_id', true);
$all_city_id = $city_id ? getAllAreaIdsWithMultiPids($city_id) : null;
if ($all_city_id){
$map[$field] = ['IN', $all_city_id];
}
}
若使用之前的权限过滤功能,用户A只能查看广东省、湖南省的数据,而属于广东省的广州市的数据会被过滤掉;
用户B可以查看的数据为空。
以下是使用此功能的步骤与效果。
- 定义回调函数getAllAreaIdsWithMultiPids,实现根据多个地区数据,返回这些地区及其下属所有地区的数据
function getAllAreaIdsWithMultiPids($city_ids, $model = 'AreaV', $max_level = 3, $need_exist = true, $cache = ''){
// 根据多个地区id获取其下属的所有地区,具体算法省略
$all_city_ids = [];
foreach ($city_ids as $v){
}
return $all_city_ids;
}
- 配置对应Model类属性$_auth_ref_rule,定义回调函数及其参数
// 配置地区AreaModel类
// 方式一:使用公共函数作为回调函数
protected $_auth_ref_rule = array(
'auth_ref_key' => 'id',
'ref_path' => 'UserArea.city_id',
'auth_ref_value_callback' => ['getAllAreaIdsWithMultiPids','__auth_ref_value__','AreaV',3,false],
'not_exists_then_ignore' => true
);
// 方式二:使用某个类的方法作为回调函数
protected $_auth_ref_rule = array(
'auth_ref_key' => 'id',
'ref_path' => 'UserArea.city_id',
'auth_ref_value_callback' => [[FullAreaModel::class,'getAllAreaIdsWithMultiPids'],'__auth_ref_value__','AreaV',3,false],
'not_exists_then_ignore' => true
);
// 配置UserAreaModel类
protected $_auth_ref_rule = array(
'auth_ref_key' => 'user_id',
'ref_path' => 'UserArea.city_id'
);
// 只需要正确配置权限链使用此功能就可以实现需求,不再需要额外代码去过滤用户的关联地区数据。
public function getArea(){
$area = D('Area')->select();
return $area;
}
若使用权限链功能,就需要设置AUTH_RULE_ID、INJECT_RBAC等值,这些标识值默认使用公共函数session管理。
但不是所有的系统都适用公共函数session,例如在前后端分离模式的系统。
对于以上系统,可以通过\Qscmf\Core\AuthChain类的registerSessionCls方法注册自定义Session类,处理标识值。
- 定义Session类,实现接口Qscmf/Core/Session/ISession
默认为\Qscmf\Core\Session\DefaultSession类,使用公共函数session管理。
class CusSession implements Session\ISession
{
public function set($key, $value)
{
session($key, $value);
}
public function get($key){
return session($key);
}
public function clear($key)
{
$this->set($key,null);
}
}
- 在app_init行为中加入注册该Session类
class AppInitBehavior extends \Think\Behavior{
public function run(&$parm){
// 其它逻辑省略...
AuthChain::registerSessionCls(CusSession::class);
}
}