400 lines
13 KiB
Markdown
Executable File
400 lines
13 KiB
Markdown
Executable File
### 重置RBAC用户表和用户与用户组关联表
|
||
#### 用法
|
||
+ config.php配置INJECT_RBAC
|
||
```blade
|
||
// 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为“别名.字段名”)
|
||
|
||
如某机构(全思小伙伴)只能查看其创建书库点的书箱,则:
|
||
```php
|
||
// 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表达式,会导致查询的层级越高,代码量就越多:
|
||
```php
|
||
// 不使用该机制,机构查询书箱数据,则需要根据登录用户获取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();
|
||
|
||
```
|
||
|
||
使用该机制后,机构查询书箱数据:
|
||
```php
|
||
D('Box')->where($map)->select();
|
||
```
|
||
|
||
### 扩展权限过滤机制
|
||
如系统存在机构用户OrgUser与书库点管理员LibraryUser,有书库点Library数据分别与他们关联(org_id与id)时,对应的LibraryModel应该这样配置:
|
||
```php
|
||
// 对于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,自定义不同用户类型的权限过滤
|
||
|
||
```php
|
||
// 机构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”对应的值
|
||
|
||
```php
|
||
|
||
// 用户类型为“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”清空对应的值
|
||
|
||
```php
|
||
// 用户类型为“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
|
||
```blade
|
||
'FRONT_AUTH_FILTER'=>true,
|
||
```
|
||
|
||
|
||
### 权限过滤机制支持配置若不存在关联数据则取消过滤
|
||
不存在关联数据时,默认返回空。使用此功能可以实现用户存在关联数据则只能查看关联的数据,若不存在关联数据则可以查看所有数据。
|
||
|
||
#### 用法
|
||
+ 配置对应Model类属性$_auth_ref_rule的not_exists_then_ignore,其值为true
|
||
```blade
|
||
该值应设置在需要获取的关联数据主表对应的Model类,如用户(UserModel类)与地区(AreaModel类)关联,需要获取地区的数据,应在AreaModel类设置该值为true。
|
||
```
|
||
|
||
```php
|
||
// 设置该值为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
|
||
```blade
|
||
auth_ref_value_callback的值为索引数组。
|
||
数组第一个元素为被调用的回调函数,若为公共函数,则为字符串;若为某个类的方法,则为数组,如[类名,方法名];
|
||
数组之后的元素是要被传入回调函数的参数。
|
||
|
||
回调函数接收关联数据的参数位置没有硬性规定,可以根据实际情况使用占位符__auth_ref_value__,程序执行时会根据ref_path的设置,获取关联字段的实际值传给回调函数,注意该值为数组。
|
||
|
||
该值应设置在需要获取的关联数据主表对应的Model类,如用户(UserModel类)与地区(AreaModel类)关联,需要获取地区的数据,应在AreaModel类设置该值。
|
||
```
|
||
|
||
```php
|
||
// 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'],
|
||
);
|
||
```
|
||
|
||
#### 场景举例
|
||
##### 若关联数据为同类型的树状结构数据,例如省市区、商品的类目,关联数据应扩展为该节点的子树。
|
||
|
||
```blade
|
||
若用户A与省市区的关联数据为:广东省、湖南省,则该用户可以查看这些省份及其下属所有地区的数据;
|
||
若用户B没有关联地区数据,则该用户可以查看所有地区的数据。
|
||
```
|
||
|
||
```php
|
||
// 不使用权限过滤功能,则每次获取用户能查看的地区数据时,都需要过滤地区数据。
|
||
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];
|
||
}
|
||
}
|
||
```
|
||
|
||
```blade
|
||
若使用之前的权限过滤功能,用户A只能查看广东省、湖南省的数据,而属于广东省的广州市的数据会被过滤掉;
|
||
用户B可以查看的数据为空。
|
||
|
||
以下是使用此功能的步骤与效果。
|
||
```
|
||
|
||
+ 定义回调函数getFullAreaIdsWithMultiPids,实现根据多个地区数据,返回这些地区及其下属所有地区的数据
|
||
|
||
```php
|
||
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,定义回调函数及其参数
|
||
```php
|
||
// 配置地区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'
|
||
);
|
||
```
|
||
|
||
```php
|
||
// 只需要正确配置权限链使用此功能就可以实现需求,不再需要额外代码去过滤用户的关联地区数据。
|
||
public function getArea(){
|
||
$area = D('Area')->select();
|
||
return $area;
|
||
}
|
||
```
|
||
|
||
### 自定义Session类用于处理权限过滤使用的标识值
|
||
```blade
|
||
若使用权限链功能,就需要设置AUTH_RULE_ID、INJECT_RBAC等值,这些标识值默认使用公共函数session管理。
|
||
|
||
但不是所有的系统都适用公共函数session,例如在前后端分离模式的系统。
|
||
|
||
对于以上系统,可以通过\Qscmf\Core\AuthChain类的registerSessionCls方法注册自定义Session类,处理标识值。
|
||
```
|
||
|
||
#### 用法
|
||
+ 定义Session类,实现接口Qscmf/Core/Session/ISession
|
||
```blade
|
||
默认为\Qscmf\Core\Session\DefaultSession类,使用公共函数session管理。
|
||
```
|
||
|
||
```php
|
||
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类
|
||
```php
|
||
class AppInitBehavior extends \Think\Behavior{
|
||
|
||
public function run(&$parm){
|
||
// 其它逻辑省略...
|
||
|
||
AuthChain::registerSessionCls(CusSession::class);
|
||
|
||
}
|
||
}
|
||
``` |