资讯详情

资讯详情

Sa-Token 实践:将权限数据放入缓存,为 StpInterface 减负

Sa-Token 实践将权限数据放入缓存为 StpInterface 减负【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架让鉴权变得简单、优雅—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token在 Sa-Token 中所有权限/角色校验最终都会回调你实现的StpInterface接口框架默认不提供权限数据缓存如果每次都查库高频鉴权接口下数据库压力会迅速放大。本文讲解如何用SaManager.getSaTokenDao()的getObject/setObjectAPI 把角色与权限数据按[账号id - 角色id - 权限列表]两级结构放入缓存并结合源码说明该缓存模型为何优于直接缓存账号权限全集的粗暴做法。读完你可以完整落地权限缓存版StpInterface实现理解其底层组件调用链并在权限变更时正确失效缓存。一、为什么框架默认不缓存权限数据先明确 Sa-Token 的鉴权数据流。权限校验 APIcheckPermission、hasPermission等并不直接查库而是委托给权限数据源加载接口StpInterface。该接口的 Javadoc 明确写明了这一设计边界见 StpInterface.java在使用权限校验 API 之前你必须实现此接口告诉框架哪些用户拥有哪些权限。框架默认不对数据进行缓存如果你的数据是从数据库中读取的一般情况下你需要手动实现数据的缓存读写。StpInterface只有两个核心方法外加一个可选的封禁判断isDisabled默认方法public interface StpInterface { /** 返回指定账号id所拥有的权限码集合 */ ListString getPermissionList(Object loginId, String loginType); /** 返回指定账号id所拥有的角色标识集合 */ ListString getRoleList(Object loginId, String loginType); /** 返回指定账号 id 是否被封禁default 方法默认不封禁 */ default SaDisableWrapperInfo isDisabled(Object loginId, String service, String loginType) { return SaDisableWrapperInfo.createNotDisabled(); } }再看调用链以 StpLogic.java 为例每次校验都会穿透到数据源// 获取指定账号的权限码集合 —— 每次调用都会进入 StpInterface public ListString getPermissionList(Object loginId) { return SaManager.getStpInterface().getPermissionList(loginId, loginType); } // 判断指定账号是否含有指定权限 —— 依赖上面的 getPermissionList public boolean hasPermission(Object loginId, String permission) { return hasElement(getPermissionList(loginId), permission); }也就是说每一个hasPermission/checkPermission/checkRole背后都是一次StpInterface回调。如果你的实现里每次都查数据库那么鉴权 QPS 就等于权限查询 QPS。另外要注意两点默认行为未实现StpInterface时所有权限校验都判负。组件获取逻辑见 SaManager.javagetStpInterface()在容器中找不到实现 Bean 时会兜底创建StpInterfaceDefaultImpl而 StpInterfaceDefaultImpl.java 的两个方法都返回空集合即用户不具有任何权限和角色。权限数据源与持久化存储解耦。Sa-Token 用SaTokenDao作为统一的读写抽象键值 对象两级 API你的缓存实现可以直接复用它而不必自己维护一套 Redis 客户端。官方 demo 中有一个不带缓存的最简实现可供对照例如 sa-token-demo-springboot 的 StpInterfaceImpl直接硬编码返回权限码与角色列表仅用于演示。生产环境下正是需要把它改造为先查缓存、未命中再查库的版本——这正是本文的主题。二、完整实现带缓存的 StpInterface下面是参考实现的核心思路与完整代码在getRoleList与getPermissionList中都先按约定 key 从SaTokenDao读缓存未命中时查库并通过setObject回填缓存有效期设为 30 天60 * 60 * 24 * 30秒。/** * 自定义权限验证接口扩展 */ Component public class StpInterfaceImpl implements StpInterface { // 返回一个账号所拥有的权限码集合 Override SuppressWarnings(unchecked) public ListString getPermissionList(Object loginId, String loginType) { // 1. 声明权限码集合 ListString list new ArrayList(); // 2. 遍历角色列表查询拥有的权限码 for (String roleId : getRoleList(loginId, loginType)) { ListString permissionList (ListString) SaManager.getSaTokenDao().getObject(satoken:role-find-permission: roleId); if (permissionList null) { // 从数据库查询这个角色 id 所拥有的权限列表 permissionList ...; // 你的数据库查询逻辑 // 查好后set 到缓存中 SaManager.getSaTokenDao().setObject(satoken:role-find-permission: roleId, permissionList, 60 * 60 * 24 * 30); } list.addAll(permissionList); } // 3. 返回权限码集合 return list; } // 返回一个账号所拥有的角色标识集合 Override SuppressWarnings(unchecked) public ListString getRoleList(Object loginId, String loginType) { ListString roleList (ListString) SaManager.getSaTokenDao().getObject(satoken:loginId-find-role: loginId); if (roleList null) { // 从数据库查询这个账号id拥有的角色列表 roleList ...; // 你的数据库查询逻辑 // 查好后set 到缓存中 SaManager.getSaTokenDao().setObject(satoken:loginId-find-role: loginId, roleList, 60 * 60 * 24 * 30); } return roleList; } }几个关键细节缓存 key 的规划satoken:loginId-find-role:{loginId}存放账号 → 角色列表satoken:role-find-permission:{roleId}存放角色 → 权限列表。前缀带业务语义便于在 Redis 中排查与批量清理。两级回源getPermissionList先取角色列表可能回源一次再逐个角色取权限列表每个角色最多回源一次。账号的权限全集是各角色权限的并集。超时时间setObject的第三个参数单位是秒示例取 30 天。SaTokenDao对 timeout 的语义在 SaTokenDao.java 中有明确定义/** * 写入 Object并设定存活时间 单位: 秒 * param timeout 存活时间值大于0时限时存储值-1时永久存储值0或小于等于-2时不存储 */ void setObject(String key, Object object, long timeout);除大于 0 的限时存储外还有-1永久存储、0或 -2不存储两种特殊值getObject未命中时返回nullSaTokenDao.java示例代码正是以 null作为回源判断依据。缓存介质由 SaTokenDao 决定。SaManager.getSaTokenDao()的兜底逻辑见 SaManager.java未注册SaTokenDao实现时会使用默认的内存实现SaTokenDaoDefaultImpl单机、随进程生命周期。在多节点部署下建议接入 Sa-Token 官方的 Redis 插件如 sa-token-plugin/sa-token-alone-redis、sa-token-plugin/sa-token-redisson此时权限缓存数据与其他 Sa-Token 会话数据共用同一 Redis无需额外引入存储。三、缓存模型设计为什么是两级而不是账号直接映射权限全集这是本文最值得借鉴的设计判断。一个直觉做法是登录时直接写入账号id - 权限列表一条缓存StpInterface中一次读取搞定// 直觉做法不推荐 // 登录时RedisUtil.setValue(账号id, 权限列表); // 校验时ListString list RedisUtil.getValue(账号id);直接粗暴却有一个严重问题文档给出的论证是系统权限架构通常是 RBAC 模型权限与用户没有直接关系而是用户拥有指定角色角色再拥有指定权限这种拥有关系是动态的随时可修改一旦修改对应关系就需要同步修改或清除缓存。假设系统中有十万个账号属于同一个角色当你调整这个角色的权限时若采用账号id - 权限列表的扁平缓存就要同时清除十万条账号级缓存——同一时间批量清除大量缓存极易引起 Redis 的缓存雪崩。而采用[账号id - 角色id - 权限列表]两级模型时只需要清除或更新角色id - 权限列表这一条缓存变更成本从 O(账号数) 降为 O(1)。一言以蔽之权限的缓存模型需要跟着权限模型走角色缓存亦然。缓存的组织结构应该镜像业务模型的共享点——角色正是权限的共享单元把它作为缓存粒度权限变更天然收敛到一个 key 上。四、上线检查清单结合本文实现与源码行为落地时建议核对检查项说明缓存失效策略修改角色-权限关系时删除satoken:role-find-permission:{roleId}修改账号-角色关系时删除satoken:loginId-find-role:{loginId}SaTokenDao 介质多节点部署时必须接入 Redis 类插件默认内存实现只在单机场景有效超时设置示例为 30 天限时存储若选择-1永久存储则完全依赖主动失效需保证失效逻辑无遗漏未命中回源getObject返回null才回源查库注意与查到空列表区分空列表也会被缓存住兜底行为确认若忘了注册StpInterfaceBean框架会回退到空集合默认实现表现为所有人无权限排查时可先看 StpInterfaceDefaultImpl.java按此结构落地后高频鉴权路径StpLogic.hasPermission→SaManager.getStpInterface()→ 你的实现绝大部分请求都只触碰缓存数据库仅在缓存过期或权限变更后被动回源从而显著降低权限校验对数据库的压力。【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架让鉴权变得简单、优雅—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →