当前位置: 首页 > article >正文

别再只写@SaCheckPermission了!手把手教你自定义Sa-Token权限校验逻辑(附源码)

深度定制Sa-Token权限体系从注解到动态数据源的进阶实践在企业级应用开发中权限管理往往需要超越简单的注解匹配。当系统演进到多租户架构、动态权限分配或复杂组织层级时标准的SaCheckPermission注解可能显得力不从心。本文将带您深入Sa-Token的扩展机制构建一套支持动态数据源、缓存优化和异常处理的生产级权限解决方案。1. 理解Sa-Token权限校验的核心机制Sa-Token的权限校验流程本质上是一个典型的注解-拦截器-策略三层架构。当我们在Controller方法上添加SaCheckPermission(user:add)时实际触发了以下处理链条注解层定义权限校验的元数据拦截层SaAnnotationInterceptor捕获请求并提取注解信息策略层SaStrategy执行具体的权限匹配逻辑关键转折点出现在StpInterface这个SPIService Provider Interface上。框架通过这个接口将权限数据的获取逻辑完全开放给开发者public interface StpInterface { ListString getPermissionList(Object loginId, String loginType); ListString getRoleList(Object loginId, String loginType); }默认实现StpInterfaceDefaultImpl使用内存静态列表这显然不适合生产环境。我们需要通过自定义实现将权限数据源转向数据库或Redis等持久化存储。2. 构建动态权限数据源2.1 基础数据库实现假设我们有以下权限表结构CREATE TABLE sys_permission ( id bigint NOT NULL, permission_code varchar(64) NOT NULL COMMENT 权限标识, permission_name varchar(64) NOT NULL COMMENT 权限名称, status tinyint NOT NULL DEFAULT 1 COMMENT 状态(1:启用 0:禁用), PRIMARY KEY (id), UNIQUE KEY idx_code (permission_code) ); CREATE TABLE sys_user_permission ( id bigint NOT NULL, user_id bigint NOT NULL, permission_id bigint NOT NULL, create_time datetime NOT NULL, PRIMARY KEY (id), KEY idx_user (user_id) );对应的Java实现类Service public class DynamicStpInterface implements StpInterface { Autowired private PermissionMapper permissionMapper; Override public ListString getPermissionList(Object loginId, String loginType) { Long userId Long.parseLong(loginId.toString()); return permissionMapper.selectUserPermissions(userId).stream() .map(PermissionDTO::getPermissionCode) .collect(Collectors.toList()); } // 角色列表实现同理 }2.2 性能优化方案直接查询数据库的方案在频繁鉴权时会产生性能问题。我们可以引入多级缓存本地缓存使用Caffeine缓存用户权限数据分布式缓存Redis存储权限数据保证集群一致性变更通知通过消息队列广播权限变更事件优化后的实现示例public ListString getPermissionList(Object loginId, String loginType) { String cacheKey user:perms: loginId; // 先查本地缓存 ListString permissions localCache.getIfPresent(cacheKey); if (permissions ! null) { return permissions; } // 再查Redis permissions redisTemplate.opsForValue().get(cacheKey); if (permissions null) { // 数据库查询 permissions loadFromDatabase(loginId); // 写入Redis redisTemplate.opsForValue().set(cacheKey, permissions, 1, TimeUnit.HOURS); } // 写入本地缓存 localCache.put(cacheKey, permissions); return permissions; }提示缓存过期时间应根据业务特点设置敏感权限建议设置较短TTL或采用实时失效机制3. 多租户权限隔离实现在SaaS系统中不同租户的权限体系需要严格隔离。我们可以在权限码中嵌入租户标识public ListString getPermissionList(Object loginId, String loginType) { UserDetail user getUserDetail(loginId); String tenantPrefix tenant: user.getTenantId() :; return permissionMapper.selectUserPermissions(user.getId()).stream() .map(perm - tenantPrefix perm.getCode()) .collect(Collectors.toList()); }对应的注解使用方式SaCheckPermission(value tenant:${currentTenant}:user:add, mode SaMode.AND) public ResponseEntity addUser(RequestBody UserDTO dto) { // 业务逻辑 }4. 高级权限控制模式4.1 基于资源的动态权限某些场景下权限需要与具体资源实例关联。例如编辑文章权限可能只针对用户自己的文章。我们可以扩展权限校验逻辑Aspect Component public class ResourcePermissionAspect { Around(annotation(resourcePerm)) public Object checkPermission(ProceedingJoinPoint joinPoint, ResourcePermission resourcePerm) throws Throwable { // 获取方法参数 Object[] args joinPoint.getArgs(); Long resourceId extractResourceId(args, resourcePerm.paramName()); // 查询资源所属用户 Long ownerId resourceService.getOwnerId(resourceId); // 当前用户要么有全局权限要么是资源所有者 if (!StpUtil.hasPermission(resourcePerm.value()) !StpUtil.getLoginIdAsLong().equals(ownerId)) { throw new NotPermissionException(resourcePerm.value()); } return joinPoint.proceed(); } }4.2 权限模板与变量替换对于规律性权限码可以使用模板减少重复定义Retention(RetentionPolicy.RUNTIME) Target(ElementType.METHOD) public interface TemplatePermission { String template(); String[] variables(); } // 使用示例 TemplatePermission( template project:${projectId}:doc:${operation}, variables {#projectId, operation} ) public ResponseEntity updateDocument( PathVariable Long projectId, RequestParam String operation) { // ... }实现解析器public class TemplatePermissionResolver { public static String resolve(String template, String[] variables, Object[] args) { String result template; for (int i 0; i variables.length; i) { String placeholder ${ variables[i] }; Object value parseVariable(variables[i], args); result result.replace(placeholder, value.toString()); } return result; } }5. 生产环境最佳实践5.1 权限变更的实时生效为了保证权限修改后及时生效需要建立完善的失效机制用户主动登出修改权限后强制相关用户重新登录缓存主动清除通过事件监听清除相应用户的权限缓存定时刷新设置合理的缓存过期时间作为兜底方案EventListener public void handlePermissionChangeEvent(PermissionChangeEvent event) { // 清除本地缓存 localCache.invalidate(user:perms: event.getUserId()); // 清除Redis缓存 redisTemplate.delete(user:perms: event.getUserId()); // 如果需要立即生效可以踢出用户 if (event.isForceLogout()) { StpUtil.logout(event.getUserId()); } }5.2 权限校验性能监控建立权限校验的性能指标监控指标名称采集方式告警阈值权限校验平均耗时拦截器记录耗时50ms数据库查询次数JDBC拦截器统计100次/分钟缓存命中率缓存组件统计90%权限校验异常次数异常处理器记录10次/分钟Around(execution(* cn.dev33.satoken.strategy.SaStrategy.checkPermission*(..))) public Object monitorPermissionCheck(ProceedingJoinPoint joinPoint) throws Throwable { long start System.currentTimeMillis(); try { return joinPoint.proceed(); } finally { long cost System.currentTimeMillis() - start; Metrics.timer(satoken.permission.check).record(cost, TimeUnit.MILLISECONDS); } }5.3 灰度发布方案当权限逻辑变更时可以采用灰度发布策略用户分群按用户ID哈希分桶版本控制在StpInterface实现中添加版本路由流量对比对比新旧版本的权限校验结果public ListString getPermissionList(Object loginId, String loginType) { // 灰度分桶 int bucket Math.abs(loginId.hashCode()) % 100; if (bucket grayPercent) { return grayVersion.getPermissionList(loginId, loginType); } else { return stableVersion.getPermissionList(loginId, loginType); } }在实际项目中我们团队曾遇到权限缓存与数据库不一致导致的生产事故。后来我们引入了双重校验机制在关键操作路径上即使用户通过权限校验执行操作前会再次确认权限状态。这种防御性编程思维在权限系统中尤为重要因为安全漏洞的代价往往远超性能损失。

相关文章:

别再只写@SaCheckPermission了!手把手教你自定义Sa-Token权限校验逻辑(附源码)

深度定制Sa-Token权限体系:从注解到动态数据源的进阶实践 在企业级应用开发中,权限管理往往需要超越简单的注解匹配。当系统演进到多租户架构、动态权限分配或复杂组织层级时,标准的SaCheckPermission注解可能显得力不从心。本文将带您深入Sa…...

rmlint重复目录合并功能详解:智能整理文件系统结构

rmlint重复目录合并功能详解:智能整理文件系统结构 【免费下载链接】rmlint Extremely fast tool to remove duplicates and other lint from your filesystem 项目地址: https://gitcode.com/gh_mirrors/rm/rmlint rmlint是一款极速的文件系统清理工具&…...

音乐解密工具终极指南:打破音乐格式壁垒,重获音频自由

音乐解密工具终极指南:打破音乐格式壁垒,重获音频自由 【免费下载链接】unlock-music 在浏览器中解锁加密的音乐文件。原仓库: 1. https://github.com/unlock-music/unlock-music ;2. https://git.unlock-music.dev/um/web 项目…...

从农田IoT设备调试到作物模型可视化,VSCode农业开发环境全栈配置,手慢无

更多请点击: https://intelliparadigm.com 第一章:VSCode农业开发环境的定位与价值 在智慧农业快速演进的背景下,VSCode 已超越传统代码编辑器角色,成为集传感器协议调试、边缘计算脚本部署、农情数据可视化与轻量级 AI 模型训练…...

Honey Select 2终极增强指南:一站式解决游戏兼容性与功能扩展问题

Honey Select 2终极增强指南:一站式解决游戏兼容性与功能扩展问题 【免费下载链接】HS2-HF_Patch Automatically translate, uncensor and update HoneySelect2! 项目地址: https://gitcode.com/gh_mirrors/hs/HS2-HF_Patch 还在为《Honey Select 2》游戏体验…...

别再只用cv2.split了!用NumPy切片拆分OpenCV图像通道,速度更快还省内存

别再只用cv2.split了!用NumPy切片拆分OpenCV图像通道,速度更快还省内存 在图像处理领域,通道拆分是最基础却高频的操作之一。许多开发者习惯性地使用cv2.split(),却不知道这个看似简单的操作背后隐藏着性能陷阱。当处理高分辨率图…...

Verilog FFT仿真与Matlab结果对比:手把手教你分析定点运算误差

Verilog FFT仿真与Matlab结果对比:定点运算误差分析与优化实战 当我们在FPGA上实现FFT算法时,定点运算带来的误差常常成为工程师面临的主要挑战之一。最近在调试一个8点FFT核时,我发现Verilog仿真结果与Matlab的理想计算结果之间存在明显差异…...

告别‘一视同仁’:聊聊CVPR 2022新作Focals Conv如何让3D检测网络学会‘看重点’

动态感知的艺术:Focal Sparse Convolution如何重塑3D物体检测的注意力机制 当激光雷达扫描一辆行驶中的汽车时,系统需要快速判断哪些点云数据真正构成了车辆轮廓,哪些只是路边的护栏或飘落的树叶。传统3D检测网络对所有数据"一视同仁&qu…...

微信聊天记录永久保存:三步构建你的个人数字记忆库

微信聊天记录永久保存:三步构建你的个人数字记忆库 【免费下载链接】WeChatMsg 提取微信聊天记录,将其导出成HTML、Word、CSV文档永久保存,对聊天记录进行分析生成年度聊天报告 项目地址: https://gitcode.com/GitHub_Trending/we/WeChatMs…...

英雄联盟智能助手:League Akari 的终极自动化工具集指南

英雄联盟智能助手:League Akari 的终极自动化工具集指南 【免费下载链接】League-Toolkit An all-in-one toolkit for LeagueClient. Gathering power 🚀. 项目地址: https://gitcode.com/gh_mirrors/le/League-Toolkit 你是否厌倦了每次游戏前繁…...

3步解决微信网页版访问限制:终极浏览器插件指南

3步解决微信网页版访问限制:终极浏览器插件指南 【免费下载链接】wechat-need-web 让微信网页版可用 / Allow the use of WeChat via webpage access 项目地址: https://gitcode.com/gh_mirrors/we/wechat-need-web 你可能会遇到这样的困境:在办公…...

OpenLyrics:foobar2000开源歌词显示面板的完整技术解析与配置指南

OpenLyrics:foobar2000开源歌词显示面板的完整技术解析与配置指南 【免费下载链接】foo_openlyrics An open-source lyric display panel for foobar2000 项目地址: https://gitcode.com/gh_mirrors/fo/foo_openlyrics OpenLyrics是一款专为foobar2000设计的…...

Unity资产逆向工程与资源管理:UABEAvalonia架构解析与实战指南

Unity资产逆向工程与资源管理:UABEAvalonia架构解析与实战指南 【免费下载链接】UABEA c# uabe for newer versions of unity 项目地址: https://gitcode.com/gh_mirrors/ua/UABEA UABEAvalonia是一款基于C#开发的跨平台Unity资产文件编辑器,专为…...

避开CH32X035 I2C开发的三个大坑:地址移位、总线忙等待和GPIO重映射详解

CH32X035 I2C开发实战:避开地址移位、总线忙等待和GPIO重映射三大陷阱 当你在CH32X035的I2C开发中遇到通信失败时,是否曾怀疑过自己的硬件连接?实际上,80%的I2C问题都源于软件配置细节。本文将带你深入三个最容易被忽视的技术陷阱…...

Honey Select 2 HF Patch:一站式汉化与增强补丁终极指南

Honey Select 2 HF Patch:一站式汉化与增强补丁终极指南 【免费下载链接】HS2-HF_Patch Automatically translate, uncensor and update HoneySelect2! 项目地址: https://gitcode.com/gh_mirrors/hs/HS2-HF_Patch 还在为Honey Select 2的日文界面感到困扰吗…...

Phaser游戏部署指南:如何将你的游戏发布到Web和移动端

Phaser游戏部署指南:如何将你的游戏发布到Web和移动端 【免费下载链接】games 一个基于Phaser的小游戏集合 项目地址: https://gitcode.com/gh_mirrors/game/games GitHub 加速计划 / game / games 是一个基于Phaser的小游戏集合,本指南将带你快速…...

ARM CoreLink NIC-400配置避坑指南:用AMBA Designer搞定AXI/AHB互连拓扑

ARM CoreLink NIC-400配置实战:用AMBA Designer构建高效AXI/AHB互连拓扑 当你在SoC设计中第一次打开AMBA Designer工具面对NIC-400的海量配置选项时,是否感到无从下手?作为ARM第四代互连IP,NIC-400的灵活性既是其最大优势&#xf…...

Firefly RK3588Q开发板Buildroot固件烧写与启动避坑全记录(附离线编译思路)

Firefly RK3588Q开发板Buildroot固件深度定制指南:从烧写到离线编译的完整实践 第一次拿到Firefly RK3588Q开发板时,我本以为像大多数嵌入式开发板一样,按照官方文档按部就班就能顺利完成系统切换。但当我试图将默认的Ubuntu系统替换为更轻量…...

3分钟完成iOS TrollStore安装:TrollInstallerX完整部署工具指南

3分钟完成iOS TrollStore安装:TrollInstallerX完整部署工具指南 【免费下载链接】TrollInstallerX A TrollStore installer for iOS 14.0 - 16.6.1 项目地址: https://gitcode.com/gh_mirrors/tr/TrollInstallerX 你是否曾经想过在iPhone上自由安装应用&…...

告别Vector工具链:用Python+PCAN-USB手把手搭建你的第一个UDS诊断脚本

告别Vector工具链:用PythonPCAN-USB手把手搭建你的第一个UDS诊断脚本 在汽车电子开发领域,诊断工具链长期被Vector等商业软件垄断,动辄数万的授权费用让个人开发者和中小团队望而却步。但鲜为人知的是,借助Python生态和PCAN-USB这…...

Element Plus + my-cron-vue3:给你的Vue3后台管理系统加个‘任务计划’功能(附完整代码)

Element Plus my-cron-vue3:构建企业级定时任务配置模块实战 在后台管理系统的开发中,定时任务配置是一个高频需求场景。无论是每天凌晨的数据统计报表生成,还是每周一次的数据库备份,甚至是每小时的缓存刷新,都需要一…...

如何为Chrome调试器编写集成测试:puppeteer测试框架实战

如何为Chrome调试器编写集成测试:puppeteer测试框架实战 【免费下载链接】vscode-chrome-debug Debug your JavaScript code running in Google Chrome from VS Code. 项目地址: https://gitcode.com/gh_mirrors/vs/vscode-chrome-debug 在现代Web开发中&…...

别再傻傻定义结构体了!用Qt的QPair轻松搞定函数多返回值(附排序与容器实战)

告别冗余代码:Qt开发者必备的QPair高效使用指南 在Qt开发中,我们经常遇到需要从函数返回多个值的场景。传统做法是定义一个临时结构体,但这往往导致代码臃肿、项目文件堆积。实际上,Qt提供了一个轻量级解决方案——QPair&#xff…...

YAJL错误处理最佳实践:如何优雅地处理解析异常

YAJL错误处理最佳实践:如何优雅地处理解析异常 【免费下载链接】yajl A fast streaming JSON parsing library in C. 项目地址: https://gitcode.com/gh_mirrors/ya/yajl YAJL(Yet Another JSON Library)作为一款高效的C语言JSON解析库…...

别光写计算器!从NOI这道基础题里,我总结出C++函数封装与错误处理的3个实用技巧

从NOI简单计算器题解看C工程化思维的3个关键跃迁 很多学过C基础语法的同学都写过计算器程序——输入两个数字和一个运算符,输出运算结果。这道出现在NOI(全国青少年信息学奥林匹克竞赛)OpenJudge平台1.4章节的"简单计算器"题目&…...

从康复评估到手势识别:sEMG特征在实际项目里到底怎么选?

从康复评估到手势识别:sEMG特征在实际项目中的选择策略 当你在开发一款基于表面肌电信号(sEMG)的假肢控制系统时,面对RMS、MAV、ZC等十几种特征参数,是否曾陷入选择困难?不同的应用场景对特征的需求差异巨大…...

【PySide6】构建实时视频监控界面:从摄像头捕获到QLabel动态显示

1. 环境准备与基础概念 在开始构建实时视频监控界面之前,我们需要先准备好开发环境。PySide6是Qt框架的Python绑定库,它提供了丰富的GUI组件和工具,非常适合用来开发桌面应用程序。OpenCV则是一个强大的计算机视觉库,能够轻松处理…...

HALCON图像与OpenCV/Numpy互转实战:打通Python视觉算法流水线的关键一步

HALCON图像与OpenCV/Numpy互转实战:打通Python视觉算法流水线的关键一步 工业视觉领域长期存在一个技术痛点:HALCON在传统机器视觉算法上的卓越性能与OpenCV/PyTorch等通用框架难以无缝协作。我曾在一个半导体缺陷检测项目中,需要将HALCON的亚…...

索尼相机终极解锁指南:OpenMemories-Tweak免费解锁隐藏功能

索尼相机终极解锁指南:OpenMemories-Tweak免费解锁隐藏功能 【免费下载链接】OpenMemories-Tweak Unlock your Sony cameras settings 项目地址: https://gitcode.com/gh_mirrors/op/OpenMemories-Tweak 你是否为索尼相机的30分钟录像限制而烦恼?…...

DDrawCompat:让经典Windows游戏在现代系统上完美运行的终极兼容方案

DDrawCompat:让经典Windows游戏在现代系统上完美运行的终极兼容方案 【免费下载链接】DDrawCompat DirectDraw and Direct3D 1-7 compatibility, performance and visual enhancements for Windows Vista, 7, 8, 10 and 11 项目地址: https://gitcode.com/gh_mirr…...