外观
权限与状态扩展
Authentication 提供权限模型和扩展契约,应用负责把角色来源、操作检查和身份状态变化连接起来。不要仅注册 Hook 就假定权限拦截或退出清理已经生效。
从角色数据到操作检查
RoleHook 提供 listRoleName(loginUser) 和 listOperationByModule(loginUser, module),分别返回角色名与模块操作集合。UserDetailHook 提供 detailKey() 和 detail(loginUser),用于描述额外身份详情。它们继承 Constant 序列 Hook 契约,但 Authentication 不收集、排序或执行这两类 Hook。
接入方式可以是:
- 应用在 AuthenticationLoginUser.permissionUser(user) 中读取自己的权限服务,再构造 DefaultPermissionUser 和 MenuOperationFunction;快速开始使用的是这种方式。
- 使用 Business 登录模块的 DefaultAuthenticationLoginUser 和 RoleHookCore 聚合业务 Hook,再由 Security 执行接口权限检查。具体业务接入仍需要该模块的依赖与配置。
系统菜单业务已经提供 SystemMenuRoleHook。菜单 code、操作字符串及客户端过滤遵循该业务实现,不由 Authentication 统一规定。仅添加系统菜单 Hook 和 Authentication,并不会自动装配 Business 登录模块的聚合器。
checkOperation(module, operations) 只返回布尔值,不自己拒绝 HTTP 请求。应用必须使用返回值或启用合适的 Security 拦截;通用回调没有内置多操作“全部满足”规则,示例和 Business 默认实现各自明确了这一策略。
装配权限时间戳服务
PermissionCacheService 保存“权限数据已变化”的时间戳。它不保存完整角色或操作集合,不扫描删除旧缓存,也不替应用重新加载权限。
应用已有 StoreClient 时,自动配置提供名为 permissionCacheStoreTemplate 的字符串模板及 PermissionCacheService。最小本地后端依赖为:
xml
<dependency>
<groupId>com.own.component</groupId>
<artifactId>springboot-component-store-starter-local</artifactId>
</dependency>版本沿用 Component BOM。多实例共享失效信号时使用共享 Store 后端,本地后端只能影响同一客户端内的数据。
| 内容 | 默认行为 |
|---|---|
| 创建条件 | 存在 StoreClient;没有 Store 时不创建模板和服务 |
| 命名空间 | 显式使用 authentication-permission,不是默认模板的 own.store.namespace |
| Codec | StoreCodecs.string() |
| 模板覆盖 | 按 Bean 名 permissionCacheStoreTemplate 缺失条件创建 |
| 服务覆盖 | 按 PermissionCacheService 类型缺失条件创建 |
多个系统共用 Redis 时,需要同时考虑权限专用命名空间和默认业务缓存命名空间。仅调整 own.store.namespace 不会改变这里的固定命名空间;需要隔离时,在存在 StoreClient 的应用中提供同名模板,例如:
java
package com.example.config;
import com.own.component.store.api.StoreClient;
import com.own.component.store.api.StoreTemplate;
import com.own.component.store.api.common.StoreCodecs;
import com.own.component.store.api.common.StoreNamespace;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class PermissionStoreConfiguration {
@Bean("permissionCacheStoreTemplate")
StoreTemplate<String> permissionCacheStoreTemplate(StoreClient client) {
return client.template(new StoreNamespace("example-permission"), StoreCodecs.string());
}
}同一应用的各实例使用一致的自定义空间;需要隔离的其他应用使用不同空间。只提供同名模板但没有 StoreClient,不会满足该自动配置的整体条件,此时应同时显式注册服务。
更新失效时间戳
| 方法 | 行为 |
|---|---|
setGlobalTimestamp() | 在逻辑键 global-timestamp 写入当前毫秒时间的十进制字符串 |
getGlobalTimestamp() | 读取该值,不存在返回 null |
setUserRoleTimestamp(userId) | 在 user-role-timestamp:<userId> 写入当前毫秒时间字符串 |
getUserRoleTimestamp(userId) | 读取该用户值,不存在返回 null |
用户 ID 必须非 null 且大于 0,非法值抛出 IllegalArgumentException。写入不指定 TTL,默认使用 Clock.systemUTC();这些值不是有序事件 ID,同一毫秒的两次更新可能相同,时钟回拨或并发跨实例写入也不保证单调递增。
应用在权限写入成功后调用相应 setter,并在读取缓存时将时间戳纳入键或版本判断,才能产生失效效果。时间戳更新不参加数据库事务,不自动延迟到提交后;需要提交后一致性时由应用编排。Store 写入异常会直接传播,没有重试或降级。
未设置 TTL 不代表永久可靠:本地容量淘汰、进程重启和后端清理都可能让值变回 null,具体见Store 容量和命名空间。读取方需要设计 null 与旧键复用时的处理,不能只依靠毫秒值承诺每次变更即时刷新。
与当前 Business 登录缓存衔接
当前 Business RoleHookCore 的模块操作缓存键包含全局时间戳、用户角色时间戳、客户端和用户 ID;角色名称缓存键只包含全局时间戳和用户 ID,没有用户角色时间戳或客户端。
因此只更新用户角色时间戳可以影响该用户的操作权限缓存键,不能保证角色名称缓存同步刷新;不同客户端的角色名也可能共享键。应用依赖实时角色名时应补充清理或调整缓存键。该差异属于实际消费者实现,Authentication 的 setter 不会替它自动补齐。
系统菜单各写入入口的刷新时点见角色与授权。Authentication 的 request 缓存仍与上述 Store 机制独立,更新 Store 时间戳不会替换当前请求已取得的权限对象。
状态清理与登录 Hook
UserStatusSpringHook 收集 Spring 提供的 UserStatusHook 列表。应用显式调用 clear(loginUser) 时,按注入列表顺序同步执行各 Hook 的 clear;需要排序可通过 Spring 的集合注入排序机制声明。没有额外的异常隔离,某项抛异常会中断后续调用,也不提供回滚。
该调度器不自动挂到退出接口、Token 过期或用户禁用事件。只有实际 Hook 才能决定清理什么;空列表时调用没有清理效果。它也不会内置清除 Authentication 的两个 request 缓存属性。应用应在可信的状态变更流程中传入正确身份并显式调用,避免把“存在这个 Bean”当作已完成退出。
| Hook 契约 | 内容与默认边界 |
|---|---|
| UserStatusHook.clear(user) | 用户状态清理动作;由上述调度器显式执行 |
| UserDetailHook | 用户详情键和值;Authentication 不执行,Business 登录流程可消费 |
| LoginSecurityHook.check(client, userId, userType, request) | 返回 flag/code/message;client() 默认 null,客户端选择与失败策略由调用方决定 |
LoginOperationHook.before() / after() | 登录前后处理契约;Authentication 没有自动调用链 |
LoginSecurityHookEntity.SUCCESS 表示 true / 00000 / 验证成功。该结果只是数据模型,不会自动抛异常、拦截请求或调用登录接口;自定义登录流程必须检查结果并决定后续行为。