laravel-org-rbac maintained by zhanghongfei
laravel-org-rbac
Laravel 12.x / 13.x 多租户、统一租户树 + RBAC:Platform / Organisation / Department / Team 同一 tenants 表;Roles / Users / Permissions;可选 用户主租户、超管、成员关系表。
环境:illuminate/* 需 ^12.0 或 ^13.0。Laravel 13 需 PHP ≥ 8.3。
测试:composer test(PHPUnit,95 用例,覆盖 DataScope(含 pivot/widest/数据范围边界)、TenantDataScope、TenantScope + BelongsToTenant、HasOrgRbacRoles、租户树模型、OrgRbacCache 等)。
适用范围(重要)
本包面向 从零搭建的 Laravel 项目(greenfield):按本包迁移建表。若你已有存量用户/RBAC/多租户方案,或需与 Spatie 等包长期并存,必须自行评估迁移与数据双写;本仓库不承诺无痛接入复杂旧系统。
缓存与 Redis(可选强制)
- 默认不强制 Redis:可用
file/database/array等默认缓存驱动;权限 TTL、memo 等照常工作。租户重绑时,非 Redis 驱动可能 无法按前缀批量删缓存(见OrgRbacCache与日志),可改用php artisan org-rbac:clear-permission-cache或生产使用 Redis。 - 推荐生产使用 Redis:便于 tag 失效 与 SCAN 清前缀。设置
CACHE_STORE=redis后,可选ORG_RBAC_REQUIRE_REDIS_CACHE=true在启动时 校验 默认缓存为 Redis(硬约束,防配置漂移)。 - Tag 失效:默认
ORG_RBAC_PERM_CACHE_USE_TAGS=true;仅当缓存驱动支持 tag 时生效,否则自动回退。
日志与合规
- 包内使用
OrgRbacLog(受config('org-rbac.logging')控制),日志上下文带org_rbac => true,可路由到独立 channel(如ORG_RBAC_LOG_CHANNEL=stack或专用 daily 文件)。 - 审计、留存、渗透与认证:见根目录
SECURITY.md(文档级实践说明,不构成法律或认证结论;正式合规需贵司法务/安全团队与持证机构定稿)。
常用环境变量
| 变量 | 说明 |
|---|---|
CACHE_STORE |
推荐生产使用 redis(非必须,见上文)。 |
ORG_RBAC_REQUIRE_REDIS_CACHE |
默认 false;设为 true 时启动期强制默认缓存为 Redis。 |
ORG_RBAC_PERM_CACHE_USE_TAGS |
默认 true(建议与 Redis 同用)。 |
ORG_RBAC_LOG_ENABLED |
默认 true;可 false 关闭包内结构化日志。 |
ORG_RBAC_LOG_CHANNEL |
非空时写入指定 Log channel。 |
ORG_RBAC_ALLOW_TENANT_HEADER |
默认 false。为 true 才允许从 HTTP Header 解析租户(须在网关剥离或仅内网使用)。 |
ORG_RBAC_TENANT_HEADER_REQUIRES_AUTH |
默认 true:未登录则忽略 Header。 |
ORG_RBAC_SUPER_ADMIN_AUDIT |
默认 false;为 true 时记录超管在各租户上下文的权限解析审计日志。 |
ORG_RBAC_REDIS_STRICT_BOOT |
在已开启「强制 Redis」时生效:默认 true 抛异常;false 仅打日志并继续启动。 |
0.7.x
- Header 租户 ID:默认 不 从 Header 解析;需
ORG_RBAC_ALLOW_TENANT_HEADER=true且建议配合 已认证用户(默认要求登录后才采纳 Header)。 - 超管审计:可选
ORG_RBAC_SUPER_ADMIN_AUDIT=true。 - Redis:默认不强制;可选
ORG_RBAC_REQUIRE_REDIS_CACHE=true+ORG_RBAC_REDIS_STRICT_BOOT。
0.6.x
- 可选强制 Redis:可在配置中开启
require_redis,启动期校验 Redis。 - 日志:
OrgRbacLog;租户解析失败/成功(debug)、租户重绑、权限缓存 flush(tag/SCAN)等可观测性。 - 测试:补充 Resolver、中间件、HTTP 路由解析、超管有效权限、
RedisCacheRequirement等用例。 - 定位:文档明确 仅建议全新项目;合规与审计见
SECURITY.md。
0.5.x
- Artisan:
php artisan org-rbac:repair-tenant-paths(修复depth/path不一致)、php artisan org-rbac:clear-permission-cache(清空本包权限缓存)。 - 权限缓存:可选
cache.use_tagged_permission_cache(需支持 tag 的 store);否则在 Redis 上使用 SCAN 按前缀删键。详见config/org-rbac.php。 - 超管:
super_admin.permission_columns控制加载权限列,避免SELECT *;在启用 TTL 时对该结果做 每用户一条 的缓存(键前缀含super.,不按租户重复),且 单次请求内 memo。 syncOrgRbacRolesInTenant:批量写入 pivot 后 只失效一次 权限缓存。- 契约:
CurrentTenantContract已在容器绑定到CurrentTenant。 - 安全说明:见仓库根目录
SECURITY.md(Header 租户解析信任边界、超管与 Policy)。
0.4.x
- 多角色
data_scope取最宽:DataScope::widest()/widestFromStrings()、DataScopeMerger,以及User::orgRbacWidestDataScopeForTenant($tenant);列表查询可用TenantDataScope::applyUsingWidestRoleScopeForUser(...)。 - 分配角色默认写入
data_scope:assignOrgRbacRoleInTenant($role, $tenant, $scope?)未传时使用配置defaults.assign_role_data_scope(默认department);syncOrgRbacRolesInTenant第三参数为同一默认。 - 租户移动:派发
TenantReparented;监听器FlushOrgRbacPermissionCacheOnTenantReparented会调用OrgRbacCache::flushAfterTenantReparent()(tag 或 Redis 前缀 SCAN)。非 Redis / 无 tag 时行为见OrgRbacCache与日志。
0.3.x 变更(相对 0.2)
- 移除独立
departments表:组织/部门/团队均为tenants树节点(parent_id、type、path、depth)。 - 权限:支持
tenant_id为空(全局)或绑定节点;含display_name、group等。 - 角色分配:
model_has_roles含assigned_at、assigned_by;保留data_scope。 - 成员:
org_rbac_tenant_user(user_id+tenant_id)。 - 继承:
orgRbacInheritedRolesForTenant/orgRbacEffectivePermissions(沿祖先租户链合并角色;可配置缓存 TTL)。 - 可选迁移:若存在
users表,可增加users.tenant_id、is_super_admin。
若你已按 0.2 跑过迁移,升级需自行处理数据迁移或在新环境安装。
安装
composer require zhanghongfei/laravel-org-rbac
php artisan vendor:publish --tag=org-rbac-config
php artisan vendor:publish --tag=org-rbac-migrations
php artisan migrate
在 config/org-rbac.php 中设置 user_model(默认可指向 App\Models\User)。
功能要点
- 物化路径
path:子树查询、祖先链;Tenant::descendants()/ancestors()。 - 组织树移动:修改
parent_id时自动重算本节点的depth、path,并按前缀批量更新所有子孙的path与depth(禁止挂到自己或子孙下)。 - 当前租户:中间件
org-rbac.tenant+CurrentTenant(路由参数{tenant}、Header、用户tenant_id等)。 - 业务表:对仅当前租户可见的数据模型使用
BelongsToTenant(tenant_id全局作用域)。 - 超管:用户存在
is_super_admin且为 true 时,hasOrgRbacPermission*恒为 true(请配合可选迁移)。
User 模型
use Zhanghongfei\OrgRbac\Concerns\HasOrgRbacRoles;
class User extends Authenticatable
{
use HasOrgRbacRoles;
protected $fillable = [/* … */, 'tenant_id', 'is_super_admin'];
}
use Zhanghongfei\OrgRbac\Enums\DataScope;
$user->hasOrgRbacPermission('posts.create');
$user->orgRbacEffectivePermissions($tenant);
$user->joinOrgRbacTenant($tenant);
$user->assignOrgRbacRoleInTenant('admin', $tenant); // pivot.data_scope 默认 department(见 config)
$user->assignOrgRbacRoleInTenant('editor', $tenant, DataScope::Subtree);
Tenant 侧成员(可选)
$tenant->members(User::class)->attach($userId, ['is_owner' => false, 'joined_at' => now()]);
业务表只有 tenant_id 时的数据范围(行级)
业务行用 外键 tenant_id → org_rbac_tenants.id(部门/团队等节点)。功能权限仍用 hasOrgRbacPermission;能看哪些行用角色 pivot 上的 data_scope(DataScope 枚举)+ TenantDataScope 收窄查询。
DataScope |
对 tenant_id 的约束 |
|---|---|
Department |
tenant_id = 当前上下文节点 |
Subtree |
tenant_id IN 当前节点.subtreeIds()(含自身) |
Tenant |
tenant_id IN 组织根子树:默认取 nearestOrganisationAncestor(),否则 rootAncestor();也可传入明确的组织根 |
Self |
若提供 user_id/created_by 等列 + 当前用户 id:tenant_id = 当前节点 且 owner 列 = 当前用户;否则退化为与 Department 相同 |
示例(列表查询):
use Zhanghongfei\OrgRbac\Enums\DataScope;
use Zhanghongfei\OrgRbac\Support\CurrentTenant;
use Zhanghongfei\OrgRbac\Support\TenantDataScope;
$tenant = app(CurrentTenant::class)->get();
$query = Post::query();
TenantDataScope::apply($query, 'tenant_id', DataScope::Subtree, $tenant);
多角色 data_scope:取最宽
继承链上多条角色、各自 pivot 上有 data_scope 时,最宽顺序为:Self < Department < Subtree < Tenant。
use Zhanghongfei\OrgRbac\Enums\DataScope;
use Zhanghongfei\OrgRbac\Support\DataScopeMerger;
use Zhanghongfei\OrgRbac\Support\TenantDataScope;
$scope = $user->orgRbacWidestDataScopeForTenant($tenant) ?? DataScope::Department;
TenantDataScope::apply($query, 'tenant_id', $scope, $tenant);
// 或一行(默认 pivot 全空时按 Department)
TenantDataScope::applyUsingWidestRoleScopeForUser($query, 'tenant_id', $user, $tenant);
也可使用 DataScope::widestFromStrings(...) / DataScopeMerger::widest(...)。
组织树移动与缓存 / 业务失效
修改某节点的 parent_id 后,库会更新该节点及子孙的 path / depth,并派发 TenantReparented。
- 本包权限缓存:
OrgRbacServiceProvider已注册FlushOrgRbacPermissionCacheOnTenantReparented:当默认缓存驱动为 Redis 时,会删除键名匹配{cache 前缀}{permission_key_prefix}*的项(见config/org-rbac.php中cache.permission_key_prefix,默认org-rbac.perm.)。可通过ORG_RBAC_FLUSH_PERM_CACHE_ON_REPARENT=false关闭。file/database等驱动无法按前缀扫描时不会删键(debug 下打日志),可改用 Redis 作缓存或手动Cache::forget。 - 事件
TenantReparented仍会派发,若你还有按租户路径或业务维度自管的缓存、搜索索引等,可在应用里自行订阅处理。
License
MIT