1. 项目概述Sa-Token登录模式实战解析登录认证是每个系统都绕不开的核心功能但不同业务场景对登录策略的需求差异巨大。金融类应用通常要求严格单地登录保障安全社交类产品则可能需要类似QQ的同端互斥登录体验。作为Java开发者我们常需要根据业务特点实现这些登录模式而Sa-Token这个轻量级权限认证框架提供了开箱即用的解决方案。我在最近的后台管理系统项目中就遇到了这样的需求管理员账号要求PC端单地登录普通员工账号允许移动端和PC端同时在线但不允许多个移动端并行登录。通过Sa-Token的灵活配置我们仅用20行代码就实现了这套复杂的登录策略。本文将带你深度剖析Sa-Token的三种典型登录模式实现方案包含单地登录、多地登录以及同端互斥登录的具体实现和底层原理。2. 核心概念与配置基础2.1 Sa-Token框架简介Sa-Token是一个轻量级的Java权限认证框架由国内开发者于2019年开源目前已成为Gitee的GVP项目。它解决了登录认证、权限认证、单点登录等系列权限相关问题最新版本已支持SpringBoot 3.x。相比Shiro和Spring SecuritySa-Token的API设计更加简洁学习曲线平缓特别适合快速开发中的权限控制需求。框架核心优势体现在会话管理自动处理Token生成/校验踢人下线内置多种下线策略注解鉴权支持方法级权限控制路由拦截适配WebFlux/Servlet环境2.2 基础环境搭建在开始实现不同登录模式前需要先完成基础环境配置。以SpringBoot 2.7.x为例!-- pom.xml 依赖配置 -- dependency groupIdcn.dev33/groupId artifactIdsa-token-spring-boot-starter/artifactId version1.34.0/version /dependency基础配置项说明application.ymlsa-token: # Token名称同时也是Cookie名称 token-name: satoken # Token有效期单位秒默认30天 timeout: 2592000 # Token临时有效期指定时间内无操作就视为token过期默认-1代表不限制 activity-timeout: -1注意生产环境务必修改默认的token-name和timeout值避免使用默认配置带来的安全风险3. 多地登录模式实现3.1 基础多地登录实现多地登录是最简单的模式也是Sa-Token的默认行为。该模式下同一账号可以在不同设备同时登录各会话互不影响。典型应用场景如企业OA系统允许员工在电脑和手机端同时在线。实现代码示例PostMapping(/login) public SaResult login(String username, String password) { // 模拟用户验证 if(admin.equals(username) 123456.equals(password)) { // 关键登录代码 StpUtil.login(10001); return SaResult.ok(登录成功).setData(StpUtil.getTokenInfo()); } return SaResult.error(登录失败); }当同一账号在不同浏览器登录时Sa-Token会为每个会话生成独立的Token。通过StpUtil.getTokenInfo()可以查看当前Token的详细信息{ tokenName: satoken, tokenValue: rtyuio45678, isLogin: true, loginId: 10001, loginType: login, tokenTimeout: 2592000, sessionTimeout: 2592000, tokenSessionTimeout: -2, tokenActivityTimeout: -1, loginDevice: default-device, tag: null }3.2 多地登录的会话管理在多地登录模式下需要特别注意会话管理策略共享会话配置sa-token: is-share: true # 默认值所有登录共用一个Token当is-share为true时所有登录会话共享同一个Token此时调用StpUtil.logout()会导致全端下线。如果希望实现单端注销需要设置为falsesa-token: is-share: false # 每次登录生成独立Token会话查询API// 获取当前账号所有登录设备 ListSaSession sessions StpUtil.searchSessionPage(0, 10, true); // 强制指定会话下线 StpUtil.kickoutSession(sessionId);实战经验对于客服系统这类需要查看用户在线状态的场景可以通过searchSessionPage接口实现在线用户列表展示4. 单地登录模式实现4.1 单地登录核心配置单地登录也叫单端登录模式下新登录会挤掉旧登录保证同一时间只有一个有效会话。这种模式常见于银行、支付类应用可以有效防止账号共享。实现只需修改一个配置sa-token: is-concurrent: false # 关闭并发登录当第二个设备登录同一账号时前一个设备的Token会被标记为已被顶下线再次访问时会收到401错误{ code: 401, msg: Token已被顶下线, data: null }4.2 单地登录的事件处理在实际业务中我们通常需要处理被挤下线的场景例如给用户友好的提示或记录安全日志。Sa-Token提供了事件监听机制EventListener public void onTokenReplaced(SaTokenReplacedEvent event) { String loginId event.getLoginId(); String device event.getDevice(); log.warn(账号{}在设备{}被顶下线, loginId, device); // 发送站内信或短信通知 messageService.send(loginId, 您的账号在另一设备登录); }关键事件说明SaTokenReplacedEventToken被顶下线时触发SaTokenKickoutEventToken被主动踢出时触发SaTokenLoginEvent登录成功时触发4.3 单地登录的进阶控制通过代码可以进一步控制单地登录的行为// 1. 获取当前账号的登录设备数 StpUtil.getSession().getTokenSignCount(); // 2. 手动顶下线其他设备保留当前设备 StpUtil.replaced(loginId); // 3. 判断当前Token是否已被顶下线 StpUtil.getTokenActiveTimeout();避坑指南在微服务环境下需要确保所有服务实例的sa-token配置一致特别是is-concurrent参数否则会导致登录状态不一致5. 同端互斥登录实现5.1 同端互斥登录原理同端互斥登录是三种模式中最复杂的它实现了同类型设备互斥如不能同时在两个手机端登录不同类型设备并行如手机和PC可以同时在线这种模式最早由QQ实现现在已成为社交应用的标配。Sa-Token通过设备标识device来实现这一机制。5.2 基础实现步骤配置关闭并发登录sa-token: is-concurrent: false登录时指定设备类型PostMapping(/login) public SaResult login(String username, String password, String device) { if(admin.equals(username) 123456.equals(password)) { // 关键代码传入device参数 StpUtil.login(10001, device); return SaResult.ok(登录成功); } return SaResult.error(登录失败); }设备类型建议使用预定义常量public interface DeviceType { String PC PC; String MOBILE MOBILE; String APP APP; String WECHAT WECHAT; }5.3 设备级会话管理同端互斥模式下设备管理API尤为重要// 1. 获取当前登录设备类型 String device StpUtil.getLoginDevice(); // 2. 将指定设备踢下线如PC端 StpUtil.kickout(10001, DeviceType.PC); // 3. 查询账号在所有设备上的登录情况 ListTokenSign tokenSigns StpUtil.getTokenSignListForLoginId(10001);设备登录信息查询结果示例[ { device: PC, token: gfdsa87654, loginTime: 1689321600 }, { device: MOBILE, token: ujmnhyt5432, loginTime: 1689321700 } ]5.4 复杂场景处理在实际项目中我们可能会遇到更复杂的需求场景1微信小程序和APP视为同类型设备// 登录时统一设备类型 String device isWechat(request) ? MOBILE : APP; StpUtil.login(userId, device);场景2平板电脑单独处理// 通过User-Agent识别设备 String userAgent request.getHeader(User-Agent); String device userAgent.contains(iPad) ? TABLET : MOBILE;场景3设备黑白名单控制// 在登录前校验设备 if(!deviceService.isAllowed(deviceType)) { throw new ApiException(该设备类型不允许登录); }6. 安全增强与最佳实践6.1 安全防护措施无论采用哪种登录模式都需要考虑以下安全措施Token加密sa-token: token-style: uuid # 可选simple-uuid、random-32、random-64、tik等二次认证// 敏感操作前验证密码 StpUtil.checkPassword(123456);异地登录检测EventListener public void onLogin(SaTokenLoginEvent event) { String ip SaHolder.getRequest().getClientIp(); if(ipService.isSuspicious(ip)) { StpUtil.kickout(event.getLoginId()); } }6.2 性能优化建议会话存储优化sa-token: # 使用Redis集中式存储 token-store-type: redis # 本地缓存减少Redis访问 is-share: trueToken前缀优化sa-token: # 按业务分前缀方便管理 token-prefix: admin:批量操作API// 批量踢出设备 StpUtil.kickoutByTokenValueList(tokenList); // 批量查询会话 StpUtil.searchTokenSession(pageSize, pageNo);6.3 监控与统计完善的登录系统需要监控机制登录统计看板// 日活统计 long dau StpUtil.searchSessionCount(0, 24); // 设备分布 MapString, Integer deviceStats tokenService.getDeviceDistribution();异常登录报警EventListener public void onLogin(SaTokenLoginEvent event) { if(loginService.isAbnormal(event)) { alertService.send(异常登录告警); } }7. 疑难问题解决方案7.1 常见报错处理Token无效问题现象频繁出现INVALID_TOKEN错误排查检查服务端和客户端的系统时间是否同步验证Token存储是否正常特别是Redis配置确认没有多个Sa-Token版本冲突会话丢失问题现象登录后偶尔会话失效解决方案sa-token: # 增加心跳超时时间 activity-timeout: 1800 # 启用自动续期 auto-renewal: true7.2 微服务场景适配在微服务架构下需要注意网关层配置// 统一处理Token转发 public class TokenFilter implements GlobalFilter { Override public MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request exchange.getRequest().mutate() .header(satoken, StpUtil.getTokenValue()) .build(); return chain.filter(exchange.mutate().request(request).build()); } }服务间调用// RPC调用时传递Token FeignClient(name user-service, configuration TokenForwardConfig.class) public interface UserService { GetMapping(/user/info) UserInfo getInfo(); }7.3 移动端特殊处理移动端需要额外考虑Token自动刷新// 拦截401错误自动刷新Token public class TokenInterceptor implements Interceptor { Override public Response intercept(Chain chain) throws IOException { Response response chain.proceed(chain.request()); if(response.code() 401) { String newToken refreshToken(); if(newToken ! null) { Request newRequest chain.request().newBuilder() .header(satoken, newToken) .build(); return chain.proceed(newRequest); } } return response; } }设备指纹生成// 生成唯一设备ID public String generateDeviceId(HttpServletRequest request) { String userAgent request.getHeader(User-Agent); String ip request.getRemoteAddr(); return DigestUtils.md5Hex(userAgent ip); }8. 实际案例电商平台登录方案设计以一个电商平台为例不同用户角色需要不同的登录策略普通用户允许3个设备同时在线同类型设备互斥重要操作需二次验证实现代码public ResultUser userLogin(LoginDTO dto) { // 1. 验证账号密码 User user userService.verify(dto); // 2. 检查设备限制 int deviceCount StpUtil.getSessionByLoginId(user.getId()) .getTokenSignList().size(); if(deviceCount 3) { throw new BusinessException(已达到最大登录设备数); } // 3. 登录带设备类型 StpUtil.login(user.getId(), dto.getDeviceType()); // 4. 返回Token信息 return Result.success(user); }商家后台严格单地登录异地登录短信验证操作日志全记录平台管理员指定IP段登录动态口令二次验证会话超时15分钟9. 扩展思考登录模式的选择策略在实际项目中选择登录模式时建议考虑以下维度安全需求金融级应用单地登录生物识别社交应用同端互斥设备信任用户体验工具类产品多地登录长期有效内容平台单地登录记住我选项业务场景客服系统多地登录会话转移协作工具同端互斥在线状态我的经验法则是先用最严格的单地登录保障基础安全再根据实际业务需求逐步放宽限制。在最近的一个医疗项目中我们就采用了动态策略工作时间单地登录非工作时间允许两地登录既保证了病历安全又兼顾了医生值班需求。
Sa-Token登录模式实战:单地、多地与同端互斥实现
1. 项目概述Sa-Token登录模式实战解析登录认证是每个系统都绕不开的核心功能但不同业务场景对登录策略的需求差异巨大。金融类应用通常要求严格单地登录保障安全社交类产品则可能需要类似QQ的同端互斥登录体验。作为Java开发者我们常需要根据业务特点实现这些登录模式而Sa-Token这个轻量级权限认证框架提供了开箱即用的解决方案。我在最近的后台管理系统项目中就遇到了这样的需求管理员账号要求PC端单地登录普通员工账号允许移动端和PC端同时在线但不允许多个移动端并行登录。通过Sa-Token的灵活配置我们仅用20行代码就实现了这套复杂的登录策略。本文将带你深度剖析Sa-Token的三种典型登录模式实现方案包含单地登录、多地登录以及同端互斥登录的具体实现和底层原理。2. 核心概念与配置基础2.1 Sa-Token框架简介Sa-Token是一个轻量级的Java权限认证框架由国内开发者于2019年开源目前已成为Gitee的GVP项目。它解决了登录认证、权限认证、单点登录等系列权限相关问题最新版本已支持SpringBoot 3.x。相比Shiro和Spring SecuritySa-Token的API设计更加简洁学习曲线平缓特别适合快速开发中的权限控制需求。框架核心优势体现在会话管理自动处理Token生成/校验踢人下线内置多种下线策略注解鉴权支持方法级权限控制路由拦截适配WebFlux/Servlet环境2.2 基础环境搭建在开始实现不同登录模式前需要先完成基础环境配置。以SpringBoot 2.7.x为例!-- pom.xml 依赖配置 -- dependency groupIdcn.dev33/groupId artifactIdsa-token-spring-boot-starter/artifactId version1.34.0/version /dependency基础配置项说明application.ymlsa-token: # Token名称同时也是Cookie名称 token-name: satoken # Token有效期单位秒默认30天 timeout: 2592000 # Token临时有效期指定时间内无操作就视为token过期默认-1代表不限制 activity-timeout: -1注意生产环境务必修改默认的token-name和timeout值避免使用默认配置带来的安全风险3. 多地登录模式实现3.1 基础多地登录实现多地登录是最简单的模式也是Sa-Token的默认行为。该模式下同一账号可以在不同设备同时登录各会话互不影响。典型应用场景如企业OA系统允许员工在电脑和手机端同时在线。实现代码示例PostMapping(/login) public SaResult login(String username, String password) { // 模拟用户验证 if(admin.equals(username) 123456.equals(password)) { // 关键登录代码 StpUtil.login(10001); return SaResult.ok(登录成功).setData(StpUtil.getTokenInfo()); } return SaResult.error(登录失败); }当同一账号在不同浏览器登录时Sa-Token会为每个会话生成独立的Token。通过StpUtil.getTokenInfo()可以查看当前Token的详细信息{ tokenName: satoken, tokenValue: rtyuio45678, isLogin: true, loginId: 10001, loginType: login, tokenTimeout: 2592000, sessionTimeout: 2592000, tokenSessionTimeout: -2, tokenActivityTimeout: -1, loginDevice: default-device, tag: null }3.2 多地登录的会话管理在多地登录模式下需要特别注意会话管理策略共享会话配置sa-token: is-share: true # 默认值所有登录共用一个Token当is-share为true时所有登录会话共享同一个Token此时调用StpUtil.logout()会导致全端下线。如果希望实现单端注销需要设置为falsesa-token: is-share: false # 每次登录生成独立Token会话查询API// 获取当前账号所有登录设备 ListSaSession sessions StpUtil.searchSessionPage(0, 10, true); // 强制指定会话下线 StpUtil.kickoutSession(sessionId);实战经验对于客服系统这类需要查看用户在线状态的场景可以通过searchSessionPage接口实现在线用户列表展示4. 单地登录模式实现4.1 单地登录核心配置单地登录也叫单端登录模式下新登录会挤掉旧登录保证同一时间只有一个有效会话。这种模式常见于银行、支付类应用可以有效防止账号共享。实现只需修改一个配置sa-token: is-concurrent: false # 关闭并发登录当第二个设备登录同一账号时前一个设备的Token会被标记为已被顶下线再次访问时会收到401错误{ code: 401, msg: Token已被顶下线, data: null }4.2 单地登录的事件处理在实际业务中我们通常需要处理被挤下线的场景例如给用户友好的提示或记录安全日志。Sa-Token提供了事件监听机制EventListener public void onTokenReplaced(SaTokenReplacedEvent event) { String loginId event.getLoginId(); String device event.getDevice(); log.warn(账号{}在设备{}被顶下线, loginId, device); // 发送站内信或短信通知 messageService.send(loginId, 您的账号在另一设备登录); }关键事件说明SaTokenReplacedEventToken被顶下线时触发SaTokenKickoutEventToken被主动踢出时触发SaTokenLoginEvent登录成功时触发4.3 单地登录的进阶控制通过代码可以进一步控制单地登录的行为// 1. 获取当前账号的登录设备数 StpUtil.getSession().getTokenSignCount(); // 2. 手动顶下线其他设备保留当前设备 StpUtil.replaced(loginId); // 3. 判断当前Token是否已被顶下线 StpUtil.getTokenActiveTimeout();避坑指南在微服务环境下需要确保所有服务实例的sa-token配置一致特别是is-concurrent参数否则会导致登录状态不一致5. 同端互斥登录实现5.1 同端互斥登录原理同端互斥登录是三种模式中最复杂的它实现了同类型设备互斥如不能同时在两个手机端登录不同类型设备并行如手机和PC可以同时在线这种模式最早由QQ实现现在已成为社交应用的标配。Sa-Token通过设备标识device来实现这一机制。5.2 基础实现步骤配置关闭并发登录sa-token: is-concurrent: false登录时指定设备类型PostMapping(/login) public SaResult login(String username, String password, String device) { if(admin.equals(username) 123456.equals(password)) { // 关键代码传入device参数 StpUtil.login(10001, device); return SaResult.ok(登录成功); } return SaResult.error(登录失败); }设备类型建议使用预定义常量public interface DeviceType { String PC PC; String MOBILE MOBILE; String APP APP; String WECHAT WECHAT; }5.3 设备级会话管理同端互斥模式下设备管理API尤为重要// 1. 获取当前登录设备类型 String device StpUtil.getLoginDevice(); // 2. 将指定设备踢下线如PC端 StpUtil.kickout(10001, DeviceType.PC); // 3. 查询账号在所有设备上的登录情况 ListTokenSign tokenSigns StpUtil.getTokenSignListForLoginId(10001);设备登录信息查询结果示例[ { device: PC, token: gfdsa87654, loginTime: 1689321600 }, { device: MOBILE, token: ujmnhyt5432, loginTime: 1689321700 } ]5.4 复杂场景处理在实际项目中我们可能会遇到更复杂的需求场景1微信小程序和APP视为同类型设备// 登录时统一设备类型 String device isWechat(request) ? MOBILE : APP; StpUtil.login(userId, device);场景2平板电脑单独处理// 通过User-Agent识别设备 String userAgent request.getHeader(User-Agent); String device userAgent.contains(iPad) ? TABLET : MOBILE;场景3设备黑白名单控制// 在登录前校验设备 if(!deviceService.isAllowed(deviceType)) { throw new ApiException(该设备类型不允许登录); }6. 安全增强与最佳实践6.1 安全防护措施无论采用哪种登录模式都需要考虑以下安全措施Token加密sa-token: token-style: uuid # 可选simple-uuid、random-32、random-64、tik等二次认证// 敏感操作前验证密码 StpUtil.checkPassword(123456);异地登录检测EventListener public void onLogin(SaTokenLoginEvent event) { String ip SaHolder.getRequest().getClientIp(); if(ipService.isSuspicious(ip)) { StpUtil.kickout(event.getLoginId()); } }6.2 性能优化建议会话存储优化sa-token: # 使用Redis集中式存储 token-store-type: redis # 本地缓存减少Redis访问 is-share: trueToken前缀优化sa-token: # 按业务分前缀方便管理 token-prefix: admin:批量操作API// 批量踢出设备 StpUtil.kickoutByTokenValueList(tokenList); // 批量查询会话 StpUtil.searchTokenSession(pageSize, pageNo);6.3 监控与统计完善的登录系统需要监控机制登录统计看板// 日活统计 long dau StpUtil.searchSessionCount(0, 24); // 设备分布 MapString, Integer deviceStats tokenService.getDeviceDistribution();异常登录报警EventListener public void onLogin(SaTokenLoginEvent event) { if(loginService.isAbnormal(event)) { alertService.send(异常登录告警); } }7. 疑难问题解决方案7.1 常见报错处理Token无效问题现象频繁出现INVALID_TOKEN错误排查检查服务端和客户端的系统时间是否同步验证Token存储是否正常特别是Redis配置确认没有多个Sa-Token版本冲突会话丢失问题现象登录后偶尔会话失效解决方案sa-token: # 增加心跳超时时间 activity-timeout: 1800 # 启用自动续期 auto-renewal: true7.2 微服务场景适配在微服务架构下需要注意网关层配置// 统一处理Token转发 public class TokenFilter implements GlobalFilter { Override public MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request exchange.getRequest().mutate() .header(satoken, StpUtil.getTokenValue()) .build(); return chain.filter(exchange.mutate().request(request).build()); } }服务间调用// RPC调用时传递Token FeignClient(name user-service, configuration TokenForwardConfig.class) public interface UserService { GetMapping(/user/info) UserInfo getInfo(); }7.3 移动端特殊处理移动端需要额外考虑Token自动刷新// 拦截401错误自动刷新Token public class TokenInterceptor implements Interceptor { Override public Response intercept(Chain chain) throws IOException { Response response chain.proceed(chain.request()); if(response.code() 401) { String newToken refreshToken(); if(newToken ! null) { Request newRequest chain.request().newBuilder() .header(satoken, newToken) .build(); return chain.proceed(newRequest); } } return response; } }设备指纹生成// 生成唯一设备ID public String generateDeviceId(HttpServletRequest request) { String userAgent request.getHeader(User-Agent); String ip request.getRemoteAddr(); return DigestUtils.md5Hex(userAgent ip); }8. 实际案例电商平台登录方案设计以一个电商平台为例不同用户角色需要不同的登录策略普通用户允许3个设备同时在线同类型设备互斥重要操作需二次验证实现代码public ResultUser userLogin(LoginDTO dto) { // 1. 验证账号密码 User user userService.verify(dto); // 2. 检查设备限制 int deviceCount StpUtil.getSessionByLoginId(user.getId()) .getTokenSignList().size(); if(deviceCount 3) { throw new BusinessException(已达到最大登录设备数); } // 3. 登录带设备类型 StpUtil.login(user.getId(), dto.getDeviceType()); // 4. 返回Token信息 return Result.success(user); }商家后台严格单地登录异地登录短信验证操作日志全记录平台管理员指定IP段登录动态口令二次验证会话超时15分钟9. 扩展思考登录模式的选择策略在实际项目中选择登录模式时建议考虑以下维度安全需求金融级应用单地登录生物识别社交应用同端互斥设备信任用户体验工具类产品多地登录长期有效内容平台单地登录记住我选项业务场景客服系统多地登录会话转移协作工具同端互斥在线状态我的经验法则是先用最严格的单地登录保障基础安全再根据实际业务需求逐步放宽限制。在最近的一个医疗项目中我们就采用了动态策略工作时间单地登录非工作时间允许两地登录既保证了病历安全又兼顾了医生值班需求。