跳转到正文

角色与授权

一次授权由“角色 → 菜单操作权限”和“用户 → 角色”两类关系组成。先维护角色及其完整权限集合,再给用户追加角色;调用格式、分页和 ID 约定见菜单管理

维护角色和权限集合

以下路径加前缀 /api/admin/system/role,权限模块为 system-role

方法与路径请求体返回业务数据操作权限
POST /allSystemRoleQuerySystemRoleVo[]查询
POST /pageSystemRoleQueryPageModel<SystemRoleVo>查询
POST /mapSystemRoleQuerySystemRoleMapVo[]查询
GET /{roleId}SystemRoleVo,补充 permissions查看详情
POST /SystemRoleDtoSystemRoleVo新增
PUT /{roleId}SystemRoleDtoSystemRoleVo修改
DELETE /{roleId}true,需要二次确认删除
PUT /disable/{roleId}/{disable}修改

Query 支持 name 模糊匹配、clientId 精确匹配和 isDisable 精确匹配,按创建时间及 ID 倒序。缺少 clientId 不会限制到当前会话客户端。

角色表单示例:

json
{
  "name": "报表查看者",
  "clientId": "web",
  "isDisable": 0,
  "permissions": [
    { "menuId": "10001", "permission": "查询,查看详情" }
  ]
}

name 必填且最多 16 字符,clientId 必填且最多 32 字符。isDisable 缺省为 0,按 % 2 处理,只应提交 01。角色名称未做唯一性检查。可提交 memo,但与菜单一样,当前 DTO 新增转换不复制备注,修改已有实体才复制。

permissions 中每项包含 roleIdmenuIdpermission;通过角色表单保存时,项中的 roleId 会被外层角色 ID 覆盖,因此可省略。menuId 应为同客户端已有菜单 ID,permission 是逗号分隔的实际授权操作;本模块不检查菜单是否存在、客户端是否一致,也不验证授权是否属于菜单声明的操作集合,应用需补齐这些规则并使用非空权限字符串。

角色修改会先删除旧权限,再插入本次列表。 permissions 省略、传 null 或空数组都会清空原授权,即使只想修改名称也必须提交要保留的完整权限集合。空字符串权限与空列表含义不同:前者仍保存关系记录。

角色详情通过后处理填充 permissionsallpage 和新增、修改响应不会额外回查该列表,不能据此判断权限已被清空。SystemRoleMapVo 只包含 idnameclientIdisDisable;完整 VO 另含备注、审计、版本及详情权限。

角色禁用接口会更新状态和缓存时间戳,但当前按用户查询角色、App 权限查询及默认权限 Hook 没有过滤角色的 isDisable。需要可靠撤权时使用解绑或移除角色权限,不能只依赖禁用状态。

绑定与解绑用户

路径前缀为 /api/admin/system/user/role,权限模块为 system-user-role

方法与路径请求体返回业务数据操作权限
GET /bind/{userId}Long[],用户绑定的全部角色 ID查询
POST /bindUserRoleBindForm修改
POST /unbindUserRoleUnbindForm修改

绑定表单:

json
{
  "userIdList": ["20001", "20002"],
  "roleIdList": ["30001"],
  "bindType": "append-update"
}

两组 ID 均为非空集合,角色会应用于每一个目标用户。HTTP 表单不允许空角色集合,bindType 仅接受以下编码:

模式实际效果
append-update保留已有角色,仅插入未查询到的用户/角色组合
force-update先删除每个目标用户的全部角色绑定,再按本次角色集合重新绑定,删除范围不区分客户端

服务只绑定能查询到的角色 ID,不会因为部分角色不存在而整体拒绝。强制模式在删除旧绑定后若查不到任何目标角色,会留下空绑定并刷新用户缓存;不要把它当成“校验失败则保留原数据”。绑定未检查目标用户是否存在、角色是否禁用或角色的客户端归属。

解绑请求同样包含非空的 userIdListroleIdList,不含 bindType,删除这些用户与角色组合的关系。管理端绑定和解绑均拒绝目标用户集合包含当前登录用户;Service 不执行这项身份校验,自定义入口需要自己执行。

GET /bind/{userId} 不按客户端过滤,也不回查角色状态;它返回绑定关系中的角色 ID,不是完整角色模型。本模块没有预置用户角色关系的通用 CRUD 或 App 绑定接口。

单独维护权限明细

需要编辑单条角色权限记录时,引入同一个管理端 JAR 即可使用 /api/admin/system/role/permission,权限模块为 system-role-permission

方法与路径请求体返回业务数据操作权限
POST /allSystemRolePermissionQuerySystemRolePermissionVo[]查询
POST /pageSystemRolePermissionQueryPageModel<SystemRolePermissionVo>查询
POST /mapSystemRolePermissionQuerySystemRolePermissionMapVo[]查询
GET /{systemRolePermissionId}SystemRolePermissionVo查看详情
POST /SystemRolePermissionDtoSystemRolePermissionVo新增
PUT /{systemRolePermissionId}SystemRolePermissionDtoSystemRolePermissionVo修改
DELETE /{systemRolePermissionId}true,需要二次确认删除

路径 ID 是权限关系 ID,不是角色 ID。DTO 字段为 roleIdmenuIdpermission,VO 增加 idcreateTime,MapVo 增加 id。这套 CRUD 没有补充必填、关系存在性或权限字符串格式校验。

当前 Query 虽声明 roleIdmenuIdpermission,Service 没有实现这些字段的查询条件,因此 /all/page/map 不会按它们筛选,也没有显式排序。按角色读取权限应使用角色详情,或在可信应用服务中调用 listByRoleId(roleId)。不能把权限明细的分页接口当成角色数据隔离入口。

独立明细 CRUD 不刷新权限缓存;角色整体保存会刷新,但存在下述时序限制。两种编辑方式不能视为缓存语义等价。

对接认证组件

默认 SystemMenuRoleHook 已随 Business 扫描注册,实现 Component 的 RoleHook

读取内容当前过程
角色名称按用户 ID 获取绑定角色,再按登录 client() 过滤角色,返回去重名称;不过滤角色禁用状态
模块操作权限获取用户的全部绑定角色 ID,以 code=moduleclientId=client()isDisable=0 查菜单,再查询这些角色在该菜单上的权限,按逗号拆分并去重;没有菜单则返回空集合

RoleHook 需要应用或 Business 登录模块聚合并接入权限用户;Authentication 本身不自动执行该 Hook,Security 才负责接口注解拦截。三者的衔接见 Authentication 权限扩展

模块操作权限这一条路径没有再按角色客户端或禁用状态筛选角色,与 App 权限接口的角色客户端过滤不同。写入时必须确保角色和菜单属于同一客户端;需要更严格隔离时,应用应调整授权服务或 Hook,不能仅依赖 App 返回数据。

管理端使用的模块名称是 system-menusystem-rolesystem-role-permissionsystem-user-role,对应常量 SYSTEM_MENUSYSTEM_ROLESYSTEM_ROLE_PERMISSIONSYSTEM_USER_ROLE。操作字符串是 查询查看详情新增修改删除,菜单排序另有 高级排序;不能把常量名 SEARCHADD 当作数据库中的操作值。

要让应用业务接口使用此数据,菜单 code 必须与接口 @PermissionModule 的值一致,角色操作字符串必须与 @PermissionOperation 的值一致,并启用应用认证权限检查。多个操作声明要求同时满足;管理端注解默认要求管理员账号类型。菜单路径与显示状态不会自动保护 HTTP 接口。

一致性与缓存刷新

变更入口缓存处理与关联数据
菜单新增、修改、删除、禁用和显隐刷新全局权限缓存时间戳;删除不清理子菜单或权限关系
菜单高级排序不刷新权限缓存
角色新增、修改基础角色写入后刷新全局时间戳,随后才替换权限集合
角色禁用刷新全局时间戳,但权限计算不据此排除角色
角色按 ID 删除先物理删除角色权限和用户绑定,再逻辑删除角色,刷新全局时间戳
用户绑定、解绑、按用户清空绑定刷新对应用户的角色缓存时间戳
按角色清空用户绑定刷新全局时间戳
独立权限 CRUD、save(roleId, permissions)deleteByRoleId(roleId)本身不刷新权限缓存

用户绑定和解绑方法有 Spring 事务;角色与权限整体保存、角色删除、菜单批量排序没有声明覆盖全部步骤的事务。追加绑定是先查后插,关系表也没有组合唯一索引,不保证并发防重。

角色保存的缓存刷新发生在权限替换之前,缓存写入也不随数据库事务回滚,不能承诺严格的提交后即时失效。要求一致性时,在应用服务中用事务编排数据库写入,并在成功提交后调用 PermissionCacheService.setGlobalTimestamp();按用户调整角色可在提交后调用 setUserRoleTimestamp(userId)。直接写 Mapper 或 SQL 同样需要补齐缓存刷新。

权限失效时间戳使用固定的 authentication-permission Store 命名空间,不随默认模板的 own.store.namespace 改变。多实例部署应共享对应后端,多应用隔离可覆盖专用模板,见 Authentication 时间戳服务

当前 Business 登录模块的角色名缓存键不包含用户角色时间戳,因此单独调用 setUserRoleTimestamp 不保证角色名同步刷新;操作权限与角色名的缓存键差异见缓存消费者边界