| 项目 | 内容 |
|---|---|
| 文档版本 | V1.0 |
| 日期 | 2026-10-21 |
| 状态 | 草稿 |
| 范围 | 用户、角色、权限、抽象 Scope、用户角色授权、Scope 绑定 |
| 关联文档 | 用户、角色、权限与 Scope 授权设计方案、六表参考 DDL |
本方案将用户、角色、权限和 Scope 授权能力收敛到统一授权插件。站点、通讯录、组网等业务插件维护业务资源及其层级;统一授权插件不复制业务 Scope 表,只保存角色、权限和资源授权绑定。业务插件不得直接写入授权表。
本方案采用以下结论:
ums_user、ums_role、ums_permission、ums_role_permission、ums_user_role_grant、ums_grant_scope。ums_grant_scope.scope_type_code 标识业务类型,scope_id 直接保存对应业务表的主键 ID;创建业务对象不需要同步创建 Scope 记录。ums_user_role_grant,在 ums_grant_scope 中有多条有效绑定。scope_type_code 必须一致。deleted_at 软删除,并使用生成列 active_unique 保证有效记录唯一、历史记录可重复保存。站点本身就是 scope_type_code=site 的 Scope,site.id 是对应的 scope_id。通讯录、组网采用相同方式映射,因此站点表不需要新增指向授权表的 scope_id 字段,也不需要同步独立的 Scope 表。站点类型、设备归属和业务生命周期仍由站点插件负责。
| 概念 | 作用 | 示例 |
|---|---|---|
| 应用 | 隔离不同管理平台的角色和权限 | system、operations |
| 权限 | 定义能够执行什么操作 | site:list、site:operator:assign |
| 角色 | 权限集合及其范围模式 | 站点管理员、运维人员 |
| Scope 类型 | 定义是哪一种业务范围 | site、contact、network |
| Scope | 具体业务范围实例 | 北京站点、研发部通讯录、全国组网 |
| Grant | 用户获得某个角色 | 张三获得运维人员角色 |
| Grant Scope | 某条 Grant 作用到的直接范围 | 张三的运维人员角色绑定北京站点 |
scope_mode 与 scope_requirement 的规则如下:
角色 scope_mode | 可配置权限 | 是否绑定 ums_grant_scope |
|---|---|---|
ALL | 两类权限均可配置 | 否;已配置的 Scope 相关权限覆盖全部有效同类型 Scope |
SCOPE_RELATED | 只能配置同 Scope 类型的 SCOPE_RELATED 权限 | 是;覆盖绑定 Scope 及其全部后代 |
SCOPE_INDEPENDENT | 只能配置 SCOPE_INDEPENDENT 权限 | 否 |
ALL 表示扩大已配置权限的范围,不表示自动拥有全部权限。
统一授权插件对外提供权限注册服务、角色服务、Grant 服务和鉴权服务。站点、通讯录、组网等业务插件按 scope_type_code 注册 ScopeResolver,供统一授权插件查询资源是否存在、是否有效以及祖先和后代范围。业务 HTTP 接口仍属于各业务插件。
flowchart LR
UI[管理界面] --> RC[角色管理 Controller]
UI --> SC[站点 Controller]
UI --> CC[通讯录 Controller]
UI --> NC[组网 Controller]
RC --> RS[RoleManagementService]
SC --> SS[SiteService]
CC --> CS[ContactService]
NC --> NS[NetworkService]
SS --> GrantSPI[GrantService SPI]
CS --> GrantSPI
NS --> GrantSPI
SS --> AuthSPI[AuthorizationService SPI]
CS --> AuthSPI
NS --> AuthSPI
RS --> RoleSPI[RoleService SPI]
RS --> PermissionSPI[PermissionService SPI]
SS -.启动时注册站点权限.-> PermissionSPI
CS -.启动时注册通讯录权限.-> PermissionSPI
NS -.启动时注册组网权限.-> PermissionSPI
SS -.注册site Resolver.-> ResolverSPI[ScopeResolverRegistry]
CS -.注册contact Resolver.-> ResolverSPI
NS -.注册network Resolver.-> ResolverSPI
subgraph AP[统一授权插件]
PermissionSPI --> AR[授权 Repository]
RoleSPI --> AR[授权 Repository]
GrantSPI --> AR
AuthSPI --> AR
GrantSPI --> ResolverSPI
AuthSPI --> ResolverSPI
end
AR --> DB[(六张授权核心表)]
SS --> SiteDB[(site及站点业务表)]
CS --> ContactDB[(通讯录业务表)]
NS --> NetworkDB[(组网业务表)]
调用约束:
ums_role、ums_user_role_grant、ums_grant_scope 等授权表。scope_type_code 和对应 ScopeResolver;Resolver 返回的业务表数据是范围存在性、状态和层级的权威来源。scope_type_code 在统一授权插件内全局唯一;即使 site.id=1 与 contact_group.id=1 同时存在,也通过不同类型形成两个独立 Scope。ums_grant_scope 不对 scope_id 建数据库外键;统一授权插件在新增绑定和鉴权时必须通过对应 Resolver 校验,不能只相信调用方传参。PermissionService 幂等注册自身权限定义;角色管理页面只把已注册权限配置给角色,不允许临时创造无法被接口使用的权限编码(这个目前可以不考虑,页面直接写死或者后端把相关页面的权限全部显示,然后操作人员进行勾选即可)。业务插件拥有业务对象,统一授权插件拥有授权数据。业务插件只负责校验业务规则并调用统一授权 SPI,不得自行写入或清理授权表。
| 业务操作 | 业务插件职责 | 统一授权插件职责 | 影响的授权表 |
|---|---|---|---|
| 创建业务对象 | 校验并创建业务记录;业务主键直接作为该类型的 scope_id | 无持久化操作;首次授权时通过 Resolver 校验资源 | 无 |
| 业务对象改名 | 更新业务名称 | 无持久化操作 | 无 |
| 移动业务对象 | 校验并更新业务树;通知授权缓存失效 | 不改授权绑定;清理相关 Scope 展开缓存 | 无 |
| 禁用或恢复业务对象 | 更新业务状态;通知授权缓存失效 | 不改授权绑定;后续鉴权通过 Resolver 读取最新状态 | 无 |
| 删除业务对象 | 校验非根节点、无子节点和无业务引用;在删除前调用 GrantService.RemoveScopeBindings | 撤销该 (scope_type_code, scope_id) 的直接绑定;清理已无其他有效 Scope 绑定的相关 Grant | 软删除 ums_grant_scope -> 按条件软删除 ums_user_role_grant |
| 添加 Scope 人员 | 校验业务对象存在及操作者权限;提交用户、角色和目标 Scope | 校验用户、角色、模式及 Scope 类型;复用或新增 Grant;新增 Grant-Scope 绑定 | 按需新增 ums_user_role_grant、新增 ums_grant_scope |
| 编辑人员角色 | 提交该用户在当前 Scope 的目标角色集合,不自行计算或写授权表 | 锁定当前直接授权,按差集新增和删除;保留其他 Scope 绑定及继承授权 | 差量更新 ums_user_role_grant、ums_grant_scope |
| 移除 Scope 人员 | 提交当前 Scope、用户和空的目标角色集合 | 撤销当前 Scope 的全部直接绑定;仅清理已无其他有效绑定的相关 Grant | 软删除 ums_grant_scope,按条件软删除 ums_user_role_grant |
| 查询权限或可见范围 | 提供登录用户、权限编码、scope_type_code 和业务主键 | 联结角色权限和 Scope 绑定,并通过 Resolver 获取资源状态及祖先或后代范围 | 只读六张授权表及对应业务表 |
其中“按条件软删除 ums_user_role_grant”只适用于 SCOPE_RELATED Grant,且必须确认该 Grant 已不存在任何有效的 ums_grant_scope。ALL 和 SCOPE_INDEPENDENT Grant 不由业务 Scope 删除操作清理。
todo :
1、ums_role_permission的parent_id代表什么?
erDiagram
ums_user ||--o{ ums_user_role_grant : "获得角色"
ums_role ||--o{ ums_user_role_grant : "被授予"
ums_role ||--o{ ums_role_permission : "配置权限"
ums_permission ||--o{ ums_role_permission : "被角色引用"
ums_user_role_grant ||--o{ ums_grant_scope : "绑定直接范围"
ums_user {
bigint id PK
varchar account UK
varchar identity_source
tinyint status
datetime deleted_at
tinyint active_unique
}
ums_role {
bigint id PK
varchar application_code
varchar code
varchar scope_mode
varchar scope_type_code
tinyint status
datetime deleted_at
tinyint active_unique
}
ums_permission {
bigint id PK
bigint parent_id FK
varchar application_code
varchar code
varchar permission_type
varchar scope_requirement
varchar scope_type_code
datetime deleted_at
tinyint active_unique
}
ums_role_permission {
bigint id PK
bigint role_id FK
bigint permission_id FK
tinyint status
datetime deleted_at
tinyint active_unique
}
ums_user_role_grant {
bigint id PK
bigint user_id FK
bigint role_id FK
tinyint status
datetime deleted_at
tinyint active_unique
}
ums_grant_scope {
bigint id PK
bigint grant_id FK
varchar scope_type_code
bigint scope_id
tinyint status
datetime deleted_at
tinyint active_unique
}
ums_grant_scope.scope_id 是多态业务引用:当 scope_type_code=site 时引用 site.id,为 contact 时引用通讯录业务表主键,为 network 时引用组网业务表主键。由于一列可能引用不同业务表,数据库不能建立统一外键,资源有效性由对应的 ScopeResolver 保证。
完整字段、索引、外键和检查约束见具体表设计 --参考 DDL。
所有表使用以下生成列:
sqlactive_unique TINYINT GENERATED ALWAYS AS (
CASE WHEN deleted_at IS NULL THEN 1 ELSE NULL END
) VIRTUAL
有效记录的 active_unique 为 1,参与唯一索引并阻止重复;软删除记录的值为 NULL。MySQL 唯一索引允许多条 NULL,因此可以保留多次授权和撤销历史。
关键有效唯一关系为:
text用户账号 (account, active_unique) 应用内角色编码 (application_code, code, active_unique) 应用内权限编码 (application_code, code, active_unique) 角色权限 (role_id, permission_id, active_unique) 用户角色Grant (user_id, role_id, active_unique) Grant直接Scope绑定 (grant_id, scope_type_code, scope_id, active_unique)
角色管理页面按应用、范围模式和 Scope 类型构造分组,不把页面分组保存为角色父子关系:
text系统管理角色 运营平台角色 ├─ 平台角色 ├─ 站点相关角色 └─ 站点无关角色
对应规则:
| 创建位置 | application_code | scope_mode | scope_type_code |
|---|---|---|---|
| 系统管理角色 | system | 通常为 SCOPE_INDEPENDENT | NULL |
| 运营平台根分组 | operations | ALL | NULL |
| 站点相关角色 | operations | SCOPE_RELATED | site |
| 站点无关角色 | operations | SCOPE_INDEPENDENT | NULL |
保存角色权限时必须校验:
application_code。SCOPE_RELATED 角色只能配置相同 scope_type_code 的相关权限。SCOPE_INDEPENDENT 角色只能配置无关权限。ALL 可配置两类权限,但只能使用明确写入 ums_role_permission 的权限。todo: 权限编码由实际提供接口的插件负责声明,例如站点插件注册 site:list、site:update 和 site:operator:assign。注册操作使用 (application_code, code) 幂等更新名称、展示树和范围要求;改变既有权限的 scope_requirement 或 scope_type_code 属于不兼容变更,必须先完成角色配置迁移,不能在运行时静默修改。
Scope 由 (scope_type_code, scope_id) 唯一描述,其中 scope_id 直接使用业务表主键:
text(site, site.id) (contact, contact_group.id) (network, network.id)
Scope 的名称、父子关系、状态和软删除状态均来自对应业务表。统一授权插件不保存这些字段,只通过注册的 ScopeResolver 查询。因此:
| 业务动作 | 授权协作 | 授权结果 |
|---|---|---|
| 创建业务对象 | 无需创建 Scope 记录 | 新对象自动成为可引用 Scope;上级授权可按业务树覆盖它 |
| 改名 | 无需调用授权服务 | 不影响授权 |
| 移动 | 通知授权服务失效该类型的范围展开缓存 | 绑定记录不变,继承范围按新业务树重新计算 |
| 禁用或恢复 | 通知授权服务失效相关缓存 | 鉴权通过 Resolver 读取最新状态 |
| 删除 | 删除前调用 GrantService.RemoveScopeBindings | 清理该业务资源的直接绑定,不影响其他业务资源 |
父子关系合法性、移动成环检查和业务状态规则由业务插件负责。ScopeResolver 必须在授权判断中返回包含目标自身的祖先链;找不到目标、目标无效或祖先无效时应失败关闭。
站点创建不再写入授权表,也不需要回填授权 Scope ID。新站点的 site.id 就是 (site, scope_id) 中的 scope_id。
sequenceDiagram
participant C as SiteController
participant S as SiteService
participant SR as SiteRepository
participant DB as MySQL
C->>S: CreateSite(request)
S->>S: 校验父站点、类别、名称和排序位置
S->>SR: 创建site
SR->>DB: INSERT site
DB-->>SR: siteID
SR-->>S: site
S-->>C: site
站点删除必须先完成业务规则和操作者权限校验,再由统一 Grant 服务清理 (site, siteID) 的直接授权绑定。站点插件不得直接写 ums_grant_scope 或 ums_user_role_grant。
sequenceDiagram
participant C as SiteController
participant S as SiteService
participant A as AuthorizationService
participant U as UnitOfWork
participant SR as SiteRepository
participant G as GrantService
participant DB as MySQL
C->>S: DeleteSite(siteID)
S->>SR: 查询site
SR->>DB: SELECT site
S->>S: 校验非根站点、无子站点、无业务引用
S->>A: Check(actor, site:delete, type=site, scopeID=siteID)
A-->>S: Allowed
S->>U: WithinTransaction
U->>SR: 锁定site并复查删除条件
SR->>DB: SELECT site FOR UPDATE
U->>G: RemoveScopeBindings(type=site, scopeID=siteID, actor)
G->>DB: 锁定并软删除该资源的直接Grant-Scope
G->>DB: 检查受影响Grant的其他有效Scope绑定
G->>DB: 软删除已无其他绑定的SCOPE_RELATED Grant
U->>SR: 软删除site
SR->>DB: UPDATE site SET deleted_at=...
U->>DB: COMMIT
S-->>C: 204
删除资源时,只在某个 SCOPE_RELATED Grant 已不存在任何其他有效绑定后才软删除该 ums_user_role_grant。若站点与授权表无法共享本地事务,则使用 Outbox 和幂等清理;清理完成前应禁止删除完成或将资源置为不可授权状态,避免留下指向不存在资源的有效绑定。
统一授权插件可拆成多个小接口注册为 SPI,但由同一个插件实现和拥有数据。
gopackage authorization
import "context"
type Actor struct {
UserID uint64
RequestID string
}
type ScopeRef struct {
ScopeTypeCode string
ScopeID int64
}
type ScopeResource struct {
ID int64
ParentID *int64
Enabled bool
}
// ScopeResolver由scope_type_code对应的业务插件实现。
type ScopeResolver interface {
Resolve(ctx context.Context, scopeID int64) (ScopeResource, error)
ListAncestorIDs(ctx context.Context, scopeID int64) ([]int64, error) // 包含自身
ListDescendantIDs(ctx context.Context, scopeID int64) ([]int64, error) // 包含自身
ListAllIDs(ctx context.Context) ([]int64, error) // 全部有效业务资源
}
type ScopeResolverRegistry interface {
Register(scopeTypeCode string, resolver ScopeResolver) error
Resolver(scopeTypeCode string) (ScopeResolver, error)
}
type PermissionDefinition struct {
ApplicationCode string
ParentCode *string
Code string
Name string
PermissionType string
ScopeRequirement string
ScopeTypeCode *string
Sort uint64
}
type Permission struct {
ID uint64
ApplicationCode string
ParentID *uint64
Code string
Name string
PermissionType string
ScopeRequirement string
ScopeTypeCode *string
Sort uint64
Status uint8
}
type PermissionService interface {
RegisterBatch(ctx context.Context, definitions []PermissionDefinition, actor Actor) error
List(ctx context.Context, applicationCode string) ([]Permission, error)
}
type Role struct {
ID uint64
ApplicationCode string
Code string
Name string
ScopeMode string
ScopeTypeCode *string
Status uint8
}
type CreateRoleCommand struct {
ApplicationCode string
Code string
Name string
ScopeMode string
ScopeTypeCode *string
Actor Actor
}
type RoleFilter struct {
ApplicationCode string
ScopeMode string
ScopeTypeCode *string
EnabledOnly bool
}
type RoleService interface {
Create(ctx context.Context, command CreateRoleCommand) (Role, error)
UpdateName(ctx context.Context, roleID uint64, name string, actor Actor) error
ReplacePermissions(ctx context.Context, roleID uint64, permissionIDs []uint64, actor Actor) error
ListAssignable(ctx context.Context, filter RoleFilter) ([]Role, error)
Disable(ctx context.Context, roleID uint64, actor Actor) error
Delete(ctx context.Context, roleID uint64, actor Actor) error
}
type ReplaceScopeRolesCommand struct {
UserID uint64
Scope ScopeRef
TargetRoleIDs []uint64 // 当前Scope编辑后的完整直接角色集合;空数组表示全部移除
Actor Actor
}
type GrantChange struct {
KeptRoleIDs []uint64
AddedRoleIDs []uint64
RemovedRoleIDs []uint64
}
type ScopeMember struct {
UserID uint64
RoleIDs []uint64
SourceScope ScopeRef
Direct bool
}
type GrantService interface {
ReplaceScopeRoles(ctx context.Context, command ReplaceScopeRolesCommand) (GrantChange, error)
ReplaceGlobalRoles(ctx context.Context, userID uint64, roleIDs []uint64, actor Actor) (GrantChange, error)
ListDirectRoles(ctx context.Context, userID uint64, scope ScopeRef) ([]Role, error)
ListScopeMembers(ctx context.Context, scope ScopeRef, includeInherited bool) ([]ScopeMember, error)
RemoveScopeBindings(ctx context.Context, scope ScopeRef, actor Actor) error
}
type CheckRequest struct {
UserID uint64
ApplicationCode string
PermissionCode string
TargetScope *ScopeRef // Scope无关权限传nil
}
type Decision struct {
Allowed bool
GrantID *uint64 // 仅用于内部审计和排障,不作为前端授权依据
}
type AccessibleScopeRequest struct {
UserID uint64
ApplicationCode string
PermissionCode string
ScopeTypeCode string
}
type AuthorizationService interface {
Check(ctx context.Context, request CheckRequest) (Decision, error)
ListAccessibleScopeIDs(ctx context.Context, request AccessibleScopeRequest) ([]int64, error)
}
接口规则:
ReplaceScopeRoles 只接受 SCOPE_RELATED 且 Scope 类型匹配的角色。ReplaceGlobalRoles 只接受 ALL 或 SCOPE_INDEPENDENT 角色,不写 ums_grant_scope。ScopeRef 由 scope_type_code 对应的 Resolver 校验,不能只相信前端候选列表。ListDirectRoles 不返回从祖先 Scope 继承的角色。ListScopeMembers(includeInherited=true) 可以展示继承人员,但继承项必须标注来源 Scope 且不可在子级直接撤销。RemoveScopeBindings 只能清理指定 (scope_type_code, scope_id) 的直接绑定,不能删除业务资源。GrantService.RemoveScopeBindings 内部契约业务插件删除资源前调用 RemoveScopeBindings。它必须加入调用方当前事务,并在同一事务中按以下顺序处理:
scope_type_code 取得 Resolver,确认目标业务资源仍存在且类型正确;无对应 Resolver 时失败关闭。(scope_type_code, scope_id) 的全部有效直接 ums_grant_scope,收集受影响的 grant_id。revoked_by、revoked_at、deleted_at;继承授权没有当前资源的直接绑定,因此不生成或删除继承记录。ums_grant_scope 绑定。scope_mode=SCOPE_RELATED 且 Grant 已无任何有效 Scope 绑定时,才撤销并软删除对应 ums_user_role_grant;仍绑定其他资源的 Grant 必须保留。RemoveScopeBindings 明确不得删除或修改以下数据:业务资源本身、父资源上产生的继承授权、同一 Grant 在其他 Scope 上的绑定、用户、角色、角色权限以及无关业务对象。上述授权清理规则只能由统一授权插件实现,业务插件不得复制。
ReplaceScopeRoles 以“当前 Scope 的直接角色集合”为基准计算差异,不读取其他 Scope 的角色,也不把继承角色作为可删除项。
sequenceDiagram
participant C as SiteOperatorController
participant S as SiteService
participant A as AuthorizationService
participant G as GrantService
participant DB as 授权数据库
C->>S: PUT /sites/{id}/operators/{userId}\nroleIds=[R2,R3]
S->>S: 校验站点
S->>A: Check(actor, site:operator:assign, type=site, scopeID=siteID)
A-->>S: Allowed
S->>G: ReplaceScopeRoles(userID, type=site, scopeID=siteID, [R2,R3])
G->>DB: 锁定当前直接Grant和绑定
G->>G: current=[R1,R2]\nkeep=[R2], add=[R3], remove=[R1]
G->>DB: 查找或新增(user,R3) Grant
G->>DB: 新增(GrantR3,site,siteID)
G->>DB: 软删除(GrantR1,site,siteID)
G->>DB: R1若无其他有效Scope则软删除GrantR1
G->>DB: COMMIT并写审计
G-->>S: GrantChange
S-->>C: 200
事务规则:
scope_type_code=当前类型 AND scope_id=当前业务ID 的有效直接绑定。(user_id, role_id) Grant;不存在则新增。(grant_id, scope_type_code, scope_id),不得修改其他 Scope 绑定。SCOPE_RELATED Grant 不再有任何有效 Scope 时,软删除该 Grant。TargetRoleIDs=[] 表示移除当前 Scope 的全部直接角色;用户账号和其他 Scope 授权保留。业务接口必须声明权限编码,并把目标业务对象映射为 ScopeRef{ScopeTypeCode, ScopeID}。统一鉴权服务按以下顺序处理:
ALL 或 SCOPE_INDEPENDENT 角色。scope_type_code 调用 Resolver,确认目标业务资源及其祖先有效,并取得包含自身的祖先业务 ID;ALL 角色不查询 ums_grant_scope,覆盖该类型全部有效业务资源;SCOPE_RELATED 角色必须从同一 Grant 找到类型相同且 scope_id 命中目标或祖先业务 ID 的直接绑定。授权表查询示例位于六表参考 DDL末尾。祖先 ID 必须先由业务 Resolver 返回,授权插件无法只查询六张授权表完成业务树递归。
列表接口应先调用:
goscopeIDs, err := authorization.ListAccessibleScopeIDs(ctx, AccessibleScopeRequest{
UserID: currentUserID,
ApplicationCode: "operations",
PermissionCode: "site:list",
ScopeTypeCode: "site",
})
ListAccessibleScopeIDs 通过 Resolver 将直接绑定展开为后代业务 ID;存在 ALL Grant 时调用 ListAllIDs。返回值可以直接用于对应业务表的主键过滤:
sqlSELECT site.*
FROM site
WHERE site.id IN (?)
AND site.deleted_at IS NULL
ORDER BY site.sort, site.id;
不能查询全部业务数据后在 Controller 逐条调用 Check,否则会产生 N+1 查询并可能在分页后过滤出错误的 total。数据量较大时,ListAccessibleScopeIDs 可返回临时表、查询条件对象或使用授权缓存;其语义保持不变。
创建角色“站点查看人员”:
textapplication_code = operations scope_mode = SCOPE_RELATED scope_type_code = site
配置唯一权限:
textpermission.code = site:list permission.scope_requirement = SCOPE_RELATED permission.scope_type_code = site
因为该角色没有 site:update、site:delete 或 site:operator:assign,即使在授权范围内也只能查询站点。
Scope 树:
text根站点 S1 ├─ 北京站点 S2 │ └─ 北京机房 S4 └─ 上海站点 S3
授权数据:
textums_user_role_grant G1 张三 站点查看人员 ums_grant_scope G1 site S2(北京站点业务ID)
结果:
| 请求 | 是否允许 | 原因 |
|---|---|---|
| 列出北京站点 | 是 | 目标等于绑定 Scope |
| 列出北京机房 | 是 | S4 是 S2 的后代 |
| 列出上海站点 | 否 | S3 是兄弟 Scope |
| 列出根站点 | 否 | 授权不向父级扩展 |
| 编辑北京站点 | 否 | 角色没有 site:update |
如果前端为了构造导航树需要显示“根站点 → 北京站点”,可以额外返回只用于路径展示的祖先节点并标注 pathOnly=true,不能因此赋予根站点的 site:list 权限。
张三又在上海站点获得相同角色时:
textums_user_role_grant G1 张三 站点查看人员 ums_grant_scope G1 site S2(北京站点业务ID) G1 site S3(上海站点业务ID)
不会新增第二条有效 (张三, 站点查看人员) Grant,只新增一条 Grant-Scope 绑定。
从北京站点移除该角色时,只软删除 G1+S2。因为 G1+S3 仍有效,G1 保持有效;张三仍能查看上海站点。
北京站点当前直接角色:
text[站点管理员 R1, 运维人员 R2]
前端提交目标集合:
json{
"roleIds": [2, 3]
}
统一 Grant 服务计算:
text保留:运维人员 R2 新增:业务运营人员 R3 删除:站点管理员 R1
只写入 R3 的新增绑定并软删除 R1 的北京站点绑定,R2 的原记录和创建时间保持不变。
前端提交:
json{
"roleIds": []
}
表示移除张三在北京站点的全部直接角色。北京站点人员列表不再显示张三;来自父站点的继承角色和张三在上海站点的绑定不受影响。
通讯录插件直接在自己的业务表中创建“研发部”,不调用授权服务创建 Scope。插件启动时注册:
textScopeResolverRegistry.Register("contact", contactScopeResolver)
给“研发部”添加人员时,统一授权插件通过 contactScopeResolver 校验通讯录节点,并写入:
textums_grant_scope(grant_id=G2, scope_type_code=contact, scope_id=研发部业务ID)
组网插件注册 network Resolver 并使用组网业务 ID。通讯录管理员不能绑定到站点 Scope,原因是角色与目标 scope_type_code 不同。各插件复用同一套 Grant 差量逻辑,无需各自实现用户角色授权表。
可缓存用户权限和 Scope 展开结果,但以下变化必须失效相关缓存:
text角色权限变化 角色状态变化 用户状态变化 Grant或Grant-Scope变化 业务资源父节点、状态或删除状态变化
缓存只用于加速。授权关系以六张授权表为准;资源存在性、状态和层级以对应业务表及 Resolver 为准。
角色权限、用户角色 Grant 和 Grant-Scope 绑定的每次变化由统一授权插件记录;业务资源生命周期由对应业务插件记录:
textrequest_id、actor_id、动作、目标类型、目标ID、变更前、变更后、时间、结果
软删除字段用于业务有效性和历史关联,不替代审计日志。
统一服务至少区分以下错误:
| 错误 | 含义 |
|---|---|
AUTH_USER_NOT_FOUND | 用户不存在、已删除或不可用 |
AUTH_ROLE_NOT_FOUND | 角色不存在或已删除 |
AUTH_ROLE_SCOPE_MISMATCH | 角色模式或 Scope 类型不匹配 |
AUTH_PERMISSION_INCOMPATIBLE | 角色与权限的范围模式不兼容 |
AUTH_SCOPE_NOT_FOUND | Scope 不存在或已删除 |
AUTH_SCOPE_DISABLED | Scope 或其祖先被禁用 |
AUTH_SCOPE_RESOLVER_NOT_FOUND | 未注册目标 scope_type_code 的 Resolver |
AUTH_FORBIDDEN | 操作人没有目标权限或目标范围 |
AUTH_CONFLICT | 并发修改或有效唯一约束冲突且无法重试 |
业务插件可以将统一错误映射成自己的稳定 HTTP 错误码,但不能把授权失败转换为成功或忽略。
| 场景 | 预期 |
|---|---|
| 创建站点 | 只新增站点业务记录,不写任何授权表 |
| 未注册目标类型的 Resolver | 鉴权和授权绑定均失败关闭 |
site.id=1 与 contact_group.id=1 同时授权 | 通过不同 scope_type_code 分别保存,不冲突 |
| 同一用户、角色绑定北京和上海 | 一条有效 Grant、两条有效 Grant-Scope |
| 北京站点移除角色,上海仍绑定 | 仅北京绑定软删除,Grant 保持有效 |
| 删除最后一个直接绑定 | 绑定和无其他范围的 Scope 相关 Grant 均软删除 |
| 子站点尝试删除父站点继承角色 | 拒绝;只能在直接授权来源 Scope 操作 |
| 北京站点授权查询北京机房 | 允许 |
| 北京站点授权查询上海站点 | 拒绝 |
只有 site:list 时编辑北京站点 | 拒绝 |
| 禁用根站点后查询北京站点 | 拒绝,即使北京站点自身仍为启用 |
| 通讯录角色绑定站点 Scope | 拒绝,Scope 类型不匹配 |
ALL角色仅配置site:list | 可查看全部有效站点,但不能编辑或删除站点 |
| 历史绑定软删除后重新授权 | 插入新的有效记录;历史记录保留且有效唯一索引不冲突 |
当前 Aurora RBAC 已有 t_user、t_role、t_role_permission 等表。实施前必须确定唯一数据源方案:
不得让 Aurora 表和 ums_* 表长期同时接受写入,否则用户状态、角色权限和授权判断会发生漂移。迁移方案、数据回填、兼容期读写策略和回滚方案需要单独形成迁移设计。
本文作者:枣子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!