1. SpringBoot3 Sa-Token 双Token认证架构解析在现代化Web应用开发中认证鉴权是系统安全的第一道防线。最近在重构一个校园网管理系统时我选择了SpringBoot3Sa-Token的组合方案特别是其双Token机制完美解决了传统单Token方案的诸多痛点。不同于常见的JWT方案Sa-Token提供了更符合国情的开箱即用功能下面分享我的实战经验。双Token机制的核心在于将访问令牌AccessToken和刷新令牌RefreshToken分离管理。AccessToken生命周期较短通常2小时用于日常接口访问RefreshToken生命周期较长通常7天专门用于Token刷新。这种设计既保证了安全性又避免了频繁登录带来的糟糕用户体验。2. 环境搭建与基础配置2.1 依赖引入与版本选择首先在pom.xml中添加必要依赖注意SpringBoot3要求Sa-Token 1.34.0!-- Sa-Token核心库 -- dependency groupIdcn.dev33/groupId artifactIdsa-token-spring-boot3-starter/artifactId version1.34.0/version /dependency !-- Redis集成用于Token持久化 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency重要提示SpringBoot3与2.x存在不兼容改动必须使用适配版本。我在初期曾因版本不匹配导致自动配置失效花费半天时间排查。2.2 基础配置项详解application.yml中需要配置的核心参数sa-token: # Token名称同时也是cookie名称 token-name: satoken # Token有效期单位秒 timeout: 7200 # 临时Token有效期RefreshToken activity-timeout: 604800 # 是否允许同一账号并发登录 is-concurrent: true # Token风格可选uuid、simple-uuid、random-32、random-64、random-128 token-style: random-64 # Redis配置 redis: database: 1 host: 127.0.0.1 port: 6379特别注意activity-timeout参数它决定了RefreshToken的有效期。在电商等高安全场景建议缩短至24小时而在内部系统可适当延长。3. 双Token登录实现细节3.1 登录接口实现PostMapping(/login) public Result login(RequestBody LoginDto dto) { // 1. 模拟数据库校验 User user userService.selectByUsername(dto.getUsername()); if(user null || !user.getPassword().equals(dto.getPassword())) { return Result.error(账号或密码错误); } // 2. 会话登录关键步骤 StpUtil.login(user.getId()); // 3. 返回双Token return Result.success() .put(accessToken, StpUtil.getTokenValue()) .put(refreshToken, StpUtil.getTokenValueByLoginId(user.getId())); }这里有几个技术要点StpUtil.login()会创建当前会话的AccessTokengetTokenValueByLoginId能获取持久化的RefreshToken实际项目需要加入密码加密校验如BCrypt3.2 Token自动续期机制Sa-Token的自动续期功能是其亮点之一。在配置activity-timeout后只要在有效期内有访问Token就会自动续期。这通过Redis的过期时间更新实现// 伪代码展示续期逻辑 public void updateTokenExpire(String tokenValue) { String key satoken:login:token: tokenValue; // 每次访问将Redis过期时间重置为activity-timeout redisTemplate.expire(key, activityTimeout, TimeUnit.SECONDS); }4. 安全防护实战技巧4.1 防止Token盗用的五层防护HTTPS加密传输必须全站HTTPS防止Token被中间人截获HttpOnlySameSite Cookie前端存储Token时设置安全标记IP绑定校验登录时记录IP关键操作校验IP一致性指纹校验通过浏览器指纹增强验证动态令牌高危操作需二次验证实现IP绑定的代码示例SaCheckLogin PostMapping(/user/update) public Result updateUser(RequestBody User user) { // 获取当前请求IP String currentIp SaHolder.getRequest().getClientIp(); // 验证IP是否与登录时一致 if(!currentIp.equals(StpUtil.getExtra(loginIp))) { StpUtil.logout(); return Result.error(异常IP访问); } // ...业务逻辑 }4.2 并发登录控制策略在多人协作场景如客服系统可能需要允许同一账号多地登录。通过配置is-concurrent: true实现// 获取指定账号的所有登录会话 ListString sessionIds StpUtil.getSessionIdListByLoginId(10001); // 强制下线特定会话 StpUtil.kickoutSession(sessionIds.get(0));5. 常见问题排查指南5.1 Token失效的六大原因Redis连接异常检查Redis服务状态和网络连接时钟不同步服务器间时间差超过Token有效期Cookie域设置错误跨域场景需正确设置domain前端存储异常localStorage被清除或IndexDB失效并发登录冲突新登录使旧Token失效手动注销检查是否有StpUtil.logout()调用5.2 跨域问题解决方案现代前后端分离项目必须处理跨域问题。推荐方案Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOrigins(https://yourdomain.com) .allowCredentials(true) // 关键允许携带Cookie .allowedMethods(*) .maxAge(3600); } }同时需要前端配合axios.defaults.withCredentials true; // 允许发送Cookie6. 性能优化实践6.1 Redis存储结构优化默认情况下Sa-Token会存储完整会话信息。对于高并发系统建议改为只存储必要数据sa-token: # 启用Token轻量化模式 is-share: true # 使用无状态模式需配合网关存储 is-stateless: false6.2 二级缓存策略通过实现SaTokenDao接口可以加入本地缓存Component public class SaTokenDaoRedisPlus implements SaTokenDao { Autowired private RedisTemplateString, Object redisTemplate; // 使用Caffeine作为一级缓存 private final CacheString, Object localCache Caffeine.newBuilder() .maximumSize(10000) .expireAfterWrite(5, TimeUnit.MINUTES) .build(); Override public Object get(String key) { // 先查本地缓存 Object value localCache.getIfPresent(key); if(value null) { value redisTemplate.opsForValue().get(key); if(value ! null) { localCache.put(key, value); } } return value; } // ...其他方法实现 }这种方案在我的项目中使Token读取性能提升了8倍适合读多写少的场景。
SpringBoot3与Sa-Token双Token认证实战指南
1. SpringBoot3 Sa-Token 双Token认证架构解析在现代化Web应用开发中认证鉴权是系统安全的第一道防线。最近在重构一个校园网管理系统时我选择了SpringBoot3Sa-Token的组合方案特别是其双Token机制完美解决了传统单Token方案的诸多痛点。不同于常见的JWT方案Sa-Token提供了更符合国情的开箱即用功能下面分享我的实战经验。双Token机制的核心在于将访问令牌AccessToken和刷新令牌RefreshToken分离管理。AccessToken生命周期较短通常2小时用于日常接口访问RefreshToken生命周期较长通常7天专门用于Token刷新。这种设计既保证了安全性又避免了频繁登录带来的糟糕用户体验。2. 环境搭建与基础配置2.1 依赖引入与版本选择首先在pom.xml中添加必要依赖注意SpringBoot3要求Sa-Token 1.34.0!-- Sa-Token核心库 -- dependency groupIdcn.dev33/groupId artifactIdsa-token-spring-boot3-starter/artifactId version1.34.0/version /dependency !-- Redis集成用于Token持久化 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency重要提示SpringBoot3与2.x存在不兼容改动必须使用适配版本。我在初期曾因版本不匹配导致自动配置失效花费半天时间排查。2.2 基础配置项详解application.yml中需要配置的核心参数sa-token: # Token名称同时也是cookie名称 token-name: satoken # Token有效期单位秒 timeout: 7200 # 临时Token有效期RefreshToken activity-timeout: 604800 # 是否允许同一账号并发登录 is-concurrent: true # Token风格可选uuid、simple-uuid、random-32、random-64、random-128 token-style: random-64 # Redis配置 redis: database: 1 host: 127.0.0.1 port: 6379特别注意activity-timeout参数它决定了RefreshToken的有效期。在电商等高安全场景建议缩短至24小时而在内部系统可适当延长。3. 双Token登录实现细节3.1 登录接口实现PostMapping(/login) public Result login(RequestBody LoginDto dto) { // 1. 模拟数据库校验 User user userService.selectByUsername(dto.getUsername()); if(user null || !user.getPassword().equals(dto.getPassword())) { return Result.error(账号或密码错误); } // 2. 会话登录关键步骤 StpUtil.login(user.getId()); // 3. 返回双Token return Result.success() .put(accessToken, StpUtil.getTokenValue()) .put(refreshToken, StpUtil.getTokenValueByLoginId(user.getId())); }这里有几个技术要点StpUtil.login()会创建当前会话的AccessTokengetTokenValueByLoginId能获取持久化的RefreshToken实际项目需要加入密码加密校验如BCrypt3.2 Token自动续期机制Sa-Token的自动续期功能是其亮点之一。在配置activity-timeout后只要在有效期内有访问Token就会自动续期。这通过Redis的过期时间更新实现// 伪代码展示续期逻辑 public void updateTokenExpire(String tokenValue) { String key satoken:login:token: tokenValue; // 每次访问将Redis过期时间重置为activity-timeout redisTemplate.expire(key, activityTimeout, TimeUnit.SECONDS); }4. 安全防护实战技巧4.1 防止Token盗用的五层防护HTTPS加密传输必须全站HTTPS防止Token被中间人截获HttpOnlySameSite Cookie前端存储Token时设置安全标记IP绑定校验登录时记录IP关键操作校验IP一致性指纹校验通过浏览器指纹增强验证动态令牌高危操作需二次验证实现IP绑定的代码示例SaCheckLogin PostMapping(/user/update) public Result updateUser(RequestBody User user) { // 获取当前请求IP String currentIp SaHolder.getRequest().getClientIp(); // 验证IP是否与登录时一致 if(!currentIp.equals(StpUtil.getExtra(loginIp))) { StpUtil.logout(); return Result.error(异常IP访问); } // ...业务逻辑 }4.2 并发登录控制策略在多人协作场景如客服系统可能需要允许同一账号多地登录。通过配置is-concurrent: true实现// 获取指定账号的所有登录会话 ListString sessionIds StpUtil.getSessionIdListByLoginId(10001); // 强制下线特定会话 StpUtil.kickoutSession(sessionIds.get(0));5. 常见问题排查指南5.1 Token失效的六大原因Redis连接异常检查Redis服务状态和网络连接时钟不同步服务器间时间差超过Token有效期Cookie域设置错误跨域场景需正确设置domain前端存储异常localStorage被清除或IndexDB失效并发登录冲突新登录使旧Token失效手动注销检查是否有StpUtil.logout()调用5.2 跨域问题解决方案现代前后端分离项目必须处理跨域问题。推荐方案Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOrigins(https://yourdomain.com) .allowCredentials(true) // 关键允许携带Cookie .allowedMethods(*) .maxAge(3600); } }同时需要前端配合axios.defaults.withCredentials true; // 允许发送Cookie6. 性能优化实践6.1 Redis存储结构优化默认情况下Sa-Token会存储完整会话信息。对于高并发系统建议改为只存储必要数据sa-token: # 启用Token轻量化模式 is-share: true # 使用无状态模式需配合网关存储 is-stateless: false6.2 二级缓存策略通过实现SaTokenDao接口可以加入本地缓存Component public class SaTokenDaoRedisPlus implements SaTokenDao { Autowired private RedisTemplateString, Object redisTemplate; // 使用Caffeine作为一级缓存 private final CacheString, Object localCache Caffeine.newBuilder() .maximumSize(10000) .expireAfterWrite(5, TimeUnit.MINUTES) .build(); Override public Object get(String key) { // 先查本地缓存 Object value localCache.getIfPresent(key); if(value null) { value redisTemplate.opsForValue().get(key); if(value ! null) { localCache.put(key, value); } } return value; } // ...其他方法实现 }这种方案在我的项目中使Token读取性能提升了8倍适合读多写少的场景。