Sa-Token框架实现记住我功能与七天免登录

📅 2026/7/18 9:23:53
Sa-Token框架实现记住我功能与七天免登录
1. Sa-Token 框架与记住我模式概述Sa-Token 是一个轻量级的 Java 权限认证框架它解决了登录认证、权限认证、单点登录等一系列权限相关问题。在实际项目中记住我功能是提升用户体验的关键特性之一。这个功能允许用户在关闭浏览器后再次访问网站时保持登录状态无需重复输入凭证。传统会话管理通常依赖服务器端的 Session 存储而 Sa-Token 采用了更现代的 Token 机制。它的核心原理是通过在客户端存储加密的 Token 来实现无状态认证这种设计既减轻了服务器压力又提高了系统的可扩展性。记住我功能的实现本质上是对 Token 生命周期的管理。当用户勾选记住我时系统会颁发一个长期有效的 Token否则只颁发一个会话级别的临时 Token。这种灵活的认证策略可以满足不同场景下的安全需求。2. 环境准备与基础配置2.1 依赖引入与初始化要在项目中使用 Sa-Token首先需要添加相关依赖。对于 Maven 项目在 pom.xml 中添加dependency groupIdcn.dev33/groupId artifactIdsa-token-spring-boot-starter/artifactId version1.34.0/version /dependency注意Spring Boot 3.x 用户需要使用 sa-token-spring-boot3-starter 替代上述依赖基础配置通常放在 application.yml 中sa-token: # Token名称 (同时也是Cookie名称) token-name: satoken # Token有效期单位秒 默认30天 timeout: 2592000 # Token临时有效期 (指定时间内无操作就视为token过期) 单位秒 activity-timeout: -1 # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) is-concurrent: true2.2 登录接口实现基础版一个最简单的登录接口实现如下RestController RequestMapping(/auth) public class AuthController { PostMapping(/login) public SaResult login(RequestParam String username, RequestParam String password, RequestParam boolean rememberMe) { // 模拟用户验证 if(admin.equals(username) 123456.equals(password)) { // 核心登录方法 StpUtil.login(10001, rememberMe); return SaResult.ok(登录成功); } return SaResult.error(登录失败); } }这个基础版本已经实现了记住我功能的核心逻辑当 rememberMe 为 true 时Token 会持久化存储为 false 时则为会话级 Token。3. 记住我功能的深度实现3.1 Cookie 机制解析Sa-Token 默认使用 Cookie 作为 Token 的传输载体其生命周期管理是记住我功能的核心临时 Cookie不设置过期时间浏览器关闭即失效持久 Cookie设置明确的过期时间如7天在有效期内持续有效在底层实现上当调用StpUtil.login(10001, true)时框架会生成一个加密的 Token 字符串将 Token 存入持久化存储如Redis通过响应头 Set-Cookie 将 Token 写入客户端设置 Cookie 的过期时间为配置的 timeout 值3.2 高级登录参数配置Sa-Token 提供了 SaLoginModel 来进行更精细的登录控制// 高级登录配置示例 StpUtil.login(10001, new SaLoginModel() .setDevice(PC) // 设备标识 .setIsLastingCookie(true) // 持久Cookie .setTimeout(604800) // 7天有效期(秒) .setToken(custom-token) // 自定义Token .setIsWriteHeader(false) // 不写入响应头 );这种配置方式特别适合需要精确控制登录行为的场景比如多端登录管理自定义 Token 生成规则特殊的 Token 传输需求3.3 前后端分离方案在前后端分离架构中通常不能依赖 Cookie此时可以采用以下方案方案1LocalStorage Authorization 头// 前端登录处理 async function login() { const res await axios.post(/auth/login, { username: admin, password: 123456, rememberMe: true }); if(res.data.code 200) { // 记住我模式使用localStorage if(rememberMe) { localStorage.setItem(satoken, res.data.token); } // 否则使用sessionStorage else { sessionStorage.setItem(satoken, res.data.token); } // 设置后续请求的Authorization头 axios.defaults.headers.common[Authorization] res.data.token; } }方案2双Token机制AccessToken RefreshToken// 服务端双Token实现 public SaResult loginWithRefresh(String username, String password, boolean rememberMe) { // 验证逻辑... // 生成访问Token短期有效 String accessToken StpUtil.createTokenValue(10001, 3600); // 1小时 // 生成刷新Token长期有效 String refreshToken rememberMe ? StpUtil.createTokenValue(10001, 604800) : // 7天 StpUtil.createTokenValue(10001, 86400); // 1天 return SaResult.data(Map.of( accessToken, accessToken, refreshToken, refreshToken, expiresIn, 3600 )); }4. 七天免登录实现细节4.1 精确控制Token有效期实现七天免登录的关键在于精确控制 Token 的有效期// 精确到秒的7天计算 int sevenDays 60 * 60 * 24 * 7; // 604800秒 // 方式1简单设置 StpUtil.login(10001, sevenDays); // 方式2通过SaLoginModel设置 StpUtil.login(10001, new SaLoginModel() .setIsLastingCookie(true) .setTimeout(sevenDays) );实际项目中建议使用 TimeUnit 枚举类来提高可读性int sevenDays (int)TimeUnit.DAYS.toSeconds(7);4.2 自动续期机制为了提升用户体验可以实现 Token 的自动续期// 配置自动续期 sa-token: timeout: 604800 # 7天有效期 auto-renewal: true # 开启自动续期 renewal-percent: 0.5 # 在有效期过去50%时续期 // 或者在代码中动态设置 StpUtil.getTokenSession().updateTimeout(sevenDays);自动续期的工作流程用户每次访问带 Token 的请求时框架检查 Token 已使用时间比例当超过 renewal-percent 阈值时自动延长有效期4.3 多端会话管理在需要支持多设备登录的场景下会话管理变得更加复杂// 获取当前账号的所有登录设备 ListSaSession sessions StpUtil.searchTokenSession(loginDeviceMobile, 1, 10); // 踢出特定设备 StpUtil.kickoutByTokenValue(xxxx-token-value); // 配置同端互斥登录 sa-token: is-concurrent: false # 不允许并发登录 is-share: false # 新登录挤掉旧登录这种机制特别适合金融类应用确保账号安全。5. 安全增强与最佳实践5.1 常见安全风险防护CSRF防护sa-token: cookie: domain: yourdomain.com same-site: lax http-only: true secure: true # HTTPS环境下启用Token防篡改Sa-Token 默认使用 SHA-256 对 Token 进行签名建议定期轮换签名密钥// 在应用启动时重置密钥 PostConstruct public void resetSecurityKey() { SaManager.getSaTokenDao().resetSecretKey(); }5.2 性能优化建议Token存储优化对于分布式系统推荐使用 Redis 作为 Token 存储配置合理的序列化方式sa-token: redis: # 使用Jackson序列化(需引入jackson依赖) serializer: jackson会话数据懒加载// 默认不加载详细会话数据 sa-token: is-lazy-load: true定期清理过期Token// 配置定时任务 Scheduled(cron 0 0 3 * * ?) public void cleanExpiredToken() { StpUtil.getSaTokenDao().clearExpiredToken(); }5.3 监控与日志添加监控端点了解认证状态management: endpoints: web: exposure: include: sa-token然后可以通过/actuator/sa-token查看当前登录数Token 分布情况认证统计信息6. 疑难问题排查指南6.1 常见问题速查表问题现象可能原因解决方案登录后立即失效Cookie域配置错误检查 sa-token.cookie.domain多端登录互相踢出is-concurrent配置为false根据需求调整配置记住我功能不生效前后端分离未正确处理存储检查Token存储方案偶尔认证失败Redis连接不稳定配置连接池和重试机制6.2 日志分析技巧启用 debug 日志获取详细认证流程logging: level: cn.dev33.satoken: debug关键日志事件Token 生成过程会话创建/销毁记录权限校验详情6.3 浏览器端检查要点Cookie 检查确保正确的 satoken Cookie 被设置检查过期时间是否符合预期请求头检查认证请求是否携带 Token在开发者工具的 Network 面板查看存储检查对于前后端分离项目检查 localStorage/sessionStorage7. 扩展功能与高级用法7.1 与Spring Security集成虽然 Sa-Token 可以独立使用但也可以与 Spring Security 配合Configuration EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .anyRequest().authenticated() .and() // 使用Sa-Token的过滤器 .addFilterBefore(new SaTokenFilter(), UsernamePasswordAuthenticationFilter.class) .csrf().disable(); } }7.2 多因素认证增强结合记住我功能实现智能的多因素认证public SaResult login(String username, String password, boolean rememberMe) { // 基础认证 if(!userService.checkPassword(username, password)) { return SaResult.error(认证失败); } // 风险评估 if(riskService.isHighRiskLogin(username)) { // 高风险操作要求二次认证 StpUtil.openSafe(120); // 开启二级认证有效期2分钟 return SaResult.error(need_verify).setData(/auth/verify); } // 正常登录 StpUtil.login(username, rememberMe); return SaResult.ok(登录成功); }7.3 微服务场景下的实现在微服务架构中通常需要网关统一处理认证// 网关过滤器示例 public class AuthFilter implements GlobalFilter { Override public MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 从请求头获取Token String token exchange.getRequest().getHeaders().getFirst(Authorization); // 验证Token if(StpUtil.checkToken(token)) { return chain.filter(exchange); } // 无效Token处理 exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } }这种架构下记住我功能对客户端是无感知的由各服务统一验证 Token 有效性。