
Sa-Token SSO 用户数据同步与迁移实战多系统账号、角色与权限对齐的三种架构方案【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架让鉴权变得简单、优雅—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token本篇技术指南以 Sa-Token 官方文档「用户数据同步 / 迁移」为核心系统讲解企业级 SSO 对接中最棘手的难题当公司已有 N 个各自独立账号体系的系统时如何让它们借助sa-token-sso认证中心实现统一登录。文章将完整拆解「统一迁移、实时同步、字段关联」三种方案并给出checkTicketAppendData、ticketResultHandle、loginId 与 centerId 转换等可直接落地的 Java 代码同时结合仓库源码揭示这些策略函数的底层实现。适用场景正在基于 Sa-Token 的 sa-token-sso 插件做 SSO 单点登录改造且各子系统已有存量用户数据、需要与认证中心对齐或关联的开发者。阅读前建议先熟悉 SSO 模式三 与 SSO 消息推送机制。一、需求背景理想化 SSO 架构与现实差距在前面的不同架构 SSO 对接示例中模式一 / 模式二 / 模式三文档均假设了一个前提所有的 sso-client 只负责业务操作不存储 user 数据user 数据全部来源于 sso-server包括登录认证也都是基于 sso-server 里的 user 账号进行校验操作。这种架构比较简洁、清晰是一种理想化的 SSO 架构模型——所有子系统的账号数据都收拢到认证中心业务端只认一个账号源头。然而更多时候我们遇到的实际情况是公司已经有了 N 多个系统每个系统都有自己独立的一套账号认证体系现在老板要让这 N 个毫无关系的系统集成单点登录。要完成这种需求首先得考虑两个问题问题一sso-client 需不需要保留 user 数据sso-client 不涉及 user 信息连表查的业务就可以不保留 user 信息sso-client 涉及 user 信息连表查业务例如帖子列表要附加显示头像昵称就需要在 sso-client 保留 user 数据。问题二如果保留的话是和 sso-server 强同步还是弱同步强同步sso-client 的 user 数据和 sso-server 的 user 数据字段值必须保持一致。例如一个用户在 server 端昵称修改为「张三」那么在 client 端也要实时同步修改弱同步两边可以各改各的。例如一个用户 server 端修改了昵称为「张三」他在 client 端依然可以叫「李四」。说明本文源自官方文档设计思路真实项目中每个公司的架构设计千差万别一套设计理论未必适应所有公司的项目。如果本篇文章的设计理念不能契合你的需求请以你公司的原设计为准。仓库中的 SSO 模式一、模式二、模式三 对接文档可作背景阅读。二、三种设计方案总览针对上述两个问题可大致分为三种设计方案方案序号方案名称简单说明适用系统方案一统一迁移统一把用户数据迁移到 sso-server 认证中心再进行对接比较简单的系统业务上不需要 user 信息连表查方案二实时同步按照一定的规则使 sso-client 和 sso-server 保持 user 信息实时同步一般业务上需要 user 信息连表查的系统方案三字段关联不同步但找一个关键字段将 sso-client 和 sso-server 的 user 账号进行关联起来sso-client 不打算过分依赖 sso-server 的 user 数据只是想借助 sso-server 完成统一登录下面逐一拆解三种方案的具体实现。三、方案一统一迁移核心思路对接工作开发前sso-client 的 user 数据完全迁移到 sso-server 中且自身不再保留 user 数据只进行业务数据处理操作。这种方案其实不必过多讲解因为数据完成迁移后整个架构就转化为了上文所述的「理想化 SSO 模型」后续对接也比较方便。迁移方式可以选择数据库同步工具或者手写代码从 sso-client 库读取数据然后 insert 到 sso-server 库中。方案优点架构简洁明了SSO 登录、注销对接起来非常方便方案缺点sso-client 不存储 user 信息因此业务上需要连表查询 user 信息的地方会比较麻烦例如拉取帖子列表时需要附加显示用户头像和昵称信息适用范围适合业务比较简单、不涉及 user 资料连表查业务的子系统。四、方案二实时同步核心思路首先对接前数据还是要迁移的只不过迁移后 sso-client 的 user 数据不删除掉依然保留。然后在项目运行阶段每当 sso-server 的 user 数据发生变动时增删改逐一向每个 sso-client 推送变化信息使 sso-client 与 sso-server 的 user 数据保持强同步。你可能会疑问那 sso-client 的 user 数据发生变动时要不要向 sso-server 推送信息官方建议尽量不要让 sso-client 的 user 信息主动发生变化。举个例子公司有电商、论坛、短视频 3 个子系统 1 个 sso-server 认证中心无论用户从哪个子系统点击「修改我的资料」按钮时都应该统一跳转到 sso-server 认证中心进行修改修改完毕后再由 sso-server 将 user 信息推送至 3 个子系统以此来保证 4 个系统间的 user 信息同步。在 Sa-Token 中「sso-server 主动通知 sso-client」这一动作正是依赖 SSO 消息推送机制 中的消息类型实现的。从源码看sso-server 端内置了checkTicketticket 校验、signout单点注销两类消息处理器而 sso-client 端内置了logoutCall单点注销回调处理器并且两端都支持通过messageHolder.addHandle(type, handler)自定义消息处理器——这为「server 向 client 推送 user 变更」提供了现成的扩展通道。方案优点sso-client 存储了 user 信息可以比较方便地进行 user 连表查操作方案缺点sso-server 与 sso-client 的 user 数据同步功能不算简单开发起来可能要耗费一段不小的工期适用范围一般业务上需要 user 信息连表查的子系统都适合。五、方案三字段关联核心方案如果子系统不需要和 sso-server 做到信息强同步可以使用字段关联法做到账户关联进行登录。举个例子公司有三个子系统——电商、论坛、短视频。同一个用户可以在这三个子系统以及 sso-server 认证中心拥有不同的昵称、头像等信息互不干扰。例如在 sso-server 认证中心里张三的数据库信息为idusernameavatarpasswordageemail10001...............10002小明cat.jpg1234561823397xx.com10003...............在电商系统里张三的数据库信息为idnameavatarmoneyemail100334............100335二明dog.jpg100023397xx.com100336............这里的关键点在于虽然用户「张三」在每个系统里的资料都是不同的但程序要想办法将它们识别为同一个用户。要做到这一点就需要准备一个关键字段将信息打通串联起来例如上表中的「邮箱」信息就可以作为这个「关联字段」。注此处仅展示使用邮箱作为关联字段的操作实际上除了邮箱以外手机号、身份证号等具有唯一性的信息都可以作为关联字段。5.1 sso-server 端重写 checkTicketAppendData 追加返回信息首先在 sso-server 端我们需要重写checkTicketAppendData函数使其在「校验 ticket 返回 loginId」时追加返回 email 字段// 配置SSO相关参数 Autowired private void configSso(SaSsoServerTemplate ssoServerTemplate) { // 其它配置 ... // 配置Ticket校验函数 ssoServerTemplate.strategy.checkTicketAppendData (loginId, result) - { System.out.println(-------- 追加返回信息到 sso-client --------); // 在校验 ticket 后给 sso-client 端追加返回信息的函数 SysUser user sysUserMapper.getById(loginId); result.set(email, user.getEmail()); // result.set(user, user); // 你也可以将整个user 对象的信息都返回到 sso-client自由决定 return result; }; }从源码看checkTicketAppendData的定义位于 SaSsoServerStrategy 中其底层函数式接口CheckTicketAppendDataFunction声明为BiFunctionObject, SaResult, SaResult即入参为「loginId 响应结果对象 SaResult」返回「追加了自定义字段后的 SaResult」——所以你可以在result上通过set(key, value)追加任意字段email、完整 user 对象等sso-client 端都会在ticketResultHandle中通过ctr.result.get(email)拿到。5.2 sso-client 端重写 ticketResultHandle 完成本地登录在 sso-client 端重写ticketResultHandle函数根据 sso-server 返回的信息查询本地 user 信息并登录// 配置SSO相关参数 Autowired private void configSso(SaSsoClientTemplate ssoClientTemplate) { // 其它配置 ... // 自定义校验 ticket 返回值的处理逻辑 每次从认证中心获取校验 ticket 的结果后调用 ssoClientTemplate.strategy.ticketResultHandle (ctr, back) - { System.out.println(--------- 自定义 ticket 校验结果处理函数 ---------); System.out.println(此账号在 sso-server 的 userId ctr.loginId); System.out.println(此账号在 sso-server 会话剩余有效期 ctr.remainSessionTimeout 秒); System.out.println(此账号返回的 email 信息 ctr.result.get(email)); // 模拟代码 // 根据 email 字段找到此账号在本系统对应的 user 信息 String email (String) ctr.result.get(email); SysUser user sysUserMapper.getByEmail(email); // 如果找不到说明是首次登录本系统的新用户需要自动注册一个新账号给他 if(user null) { // 涉及到数据库操作此处仅做模拟代码 // 1、构建 user 信息 // 2、插入到数据库 // 3、查询出最新刚插入的这条 user 信息 user sysUserMapper.getByEmail(email); } // 进行登录 StpUtil.login(user.getId(), ctr.remainSessionTimeout); StpUtil.getSession().set(user, user); // 一切工作完毕重定向回 back 页面 return SaHolder.getResponse().redirect(back); }; }ticketResultHandle是sso-client端每次从认证中心获取校验 ticket 结果后都会调用的钩子其函数式接口签名位于 TicketResultHandleFunctionrun(SaCheckTicketResult ctr, String back)。这里ctr类型SaCheckTicketResult承载了校验 ticket 后的全部返回信息从 SaCheckTicketResult 源码可知它包含以下字段供你灵活取用字段含义loginIdsso-server 端的账号 idcenterId此账号在认证中心的 loginIdtokenValue在 sso-server 端的 token 值deviceId登录设备 idremainTokenTimeout此账号 token 剩余有效期秒remainSessionTimeout此账号会话剩余有效期秒result从 sso-server 返回的原生所有参数含checkTicketAppendData追加的字段方案三优缺点小结方案优点sso-client 不需要和 sso-server 保持信息强同步实现起来不复杂架构也比较清晰易维护同一个用户的信息sso-client 可以和 sso-server 保持不同各自维护各自的互不干扰。方案缺点好像没啥缺点除非你觉着上述第 2 条优点属于缺点。适用范围在 user 信息方面不打算过分依赖 sso-server 的系统希望自己维护自己的 user 信息只是想借助 sso-server 完成一下统一登录。六、扩展没有关联字段怎么办center_id 方案如果子系统的 user 表没有邮箱、手机号等唯一性字段和 sso-server 的 user 表进行关联该怎么办呢没有字段那就创造个字段例如在子系统 user 表新增一列center_id记录这个用户在认证中心所属的账号 ididnameavataragecenter_id205421............205422小风筝dog.jpg2110002205423............如上表所示center_id记录了这个用户在认证中心所属的账号 id此处为 10002登录时根据这个center_id来查找相应的用户。由于 sso-server 端默认就会返回 loginId 参数因此在 sso-server 端不必再重写checkTicketAppendData函数来追加返回信息了我们只需要重写 sso-client 端的ticketResultHandle函数即可// 配置SSO相关参数 Autowired private void configSso(SaSsoClientTemplate ssoClientTemplate) { // 其它配置 ... // 自定义校验 ticket 返回值的处理逻辑 每次从认证中心获取校验 ticket 的结果后调用 ssoClientTemplate.strategy.ticketResultHandle (ctr, back) - { System.out.println(--------- 自定义 ticket 校验结果处理函数 ---------); System.out.println(此账号在 sso-server 的 userId ctr.loginId); System.out.println(此账号在 sso-server 会话剩余有效期 ctr.remainSessionTimeout 秒); // 模拟代码 // 根据 center_id 字段找到此账号在本系统对应的 user 信息 long centerId SaFoxUtil.getValueByType(ctr.loginId, long.class); SysUser user sysUserMapper.getByCenterId(centerId); // 如果找不到说明是首次登录本系统的新用户需要自动注册一个新账号给他 if(user null) { // 涉及到数据库操作此处仅做模拟 // 1、构建 user 信息 // 2、插入到数据库 // 3、查询出最新刚插入的这条 user 信息 user sysUserMapper.getByCenterId(userId); } // 进行登录 // 注意此处需要使用 centerId 进行登录否则该账号将无法正常完成单点注销功能 StpUtil.login(centerId, ctr.remainSessionTimeout); StpUtil.getSession().set(user, user); // 一切工作完毕重定向回 back 页面 return SaHolder.getResponse().redirect(back); }; }代码中有两处关键细节值得注意SaFoxUtil.getValueByType(ctr.loginId, long.class)负责把 sso-server 返回的 loginId可能是字符串等类型安全转换为long类型再去数据库按center_id查询登录时必须使用centerId 进行登录而非本地业务 user 的 id否则该账号将无法正常完成单点注销功能——这一点在下一节会展开解释。七、方案三完整登录流程解析按照方案三一个用户登录过程中sso-server 和 sso-client 对这个用户账号的完整处理步骤如下用户进入 sso-client 登录页面点击「使用 xx 认证中心快捷登录」按钮浏览器跳转至 sso-server 认证中心如果用户在 sso-server 有账号则直接登录如果没有则注册账号并登录sso-server 重定向回 sso-client 端并携带 ticket 参数sso-client 获取 ticket 参数并解析出 center_id 值根据 center_id 从 user 表查数据5.1 查得到证明有账号直接登录5.2 查不到证明无账号程序自动给他添加一条 user 账号并登录登录完成。整个流程的本质就是「认证中心负责认证、本地端负责按关联字段落地账号」sso-server 端通过 checkTicket 消息处理器 完成 ticket 校验并返回 loginIdsso-client 端则在ticketResultHandle中完成「查询 → 自动注册 → 登录」的完整闭环。八、解决方案三下 loginId 与 centerId 不一致的问题按照字段关联法登录之后如果一个用户在本地应用端的 userId 和认证中心端的 userId 不一致则可能发生单点注销失败的情况。假设一个用户在认证中心的 userId10002在本地应用端的 userId100335。则在本地应用端发起单点注销时其传递的 loginId 值是 100335sso-server 是找不到 userId100335 用户的自然无法单点注销成功。解决方案是在本地应用端重写loginId 与 centerId 转换策略函数做到本地应用 userId 与认证中心 userId 的互相映射RestController public class SsoClientController { // 配置SSO相关参数 Autowired private void configSso(SaSsoClientTemplate ssoClientTemplate) { // 重写 loginId 与 centerId 转换策略函数做到本地应用 userId 与认证中心 userId 的互相映射 // 将 centerId 转换为 loginId 的函数 ssoClientTemplate.strategy.convertCenterIdToLoginId (centerId) - { return Stu centerId; }; // 将 loginId 转换为 centerId 的函数 ssoClientTemplate.strategy.convertLoginIdToCenterId (loginId) - { return loginId.toString().substring(3); }; } }如上代码演示了应用本地 loginId 与认证中心 centerId 不一致时的转换写法演示逻辑为添加和裁剪指定前缀。真实项目中应该根据用户表存储的映射关系来做查询返回。从源码看这两个转换函数定义在 SaSsoClientStrategy 中默认实现均为恒等映射centerId - centerId、loginId - loginId。它们已被底层核心链路自动调用在 SaSsoClientTemplate.ssoLogout() 方法中单点注销前会先执行strategy.convertLoginIdToCenterId.run(loginId)将本地 loginId 转换为认证中心 id再构建 signout 消息推送至 sso-server——这就是「登录用 centerId、注销也按转换策略提交」的原因两者必须一一对应才能保证单点注销链路完整。值得注意的是在重写转换策略后我们在消息推送时也应该严格按照转换写法提交 loginId 参数例如// 查询我的账号信息sso-client 前端 - sso-center 后端 - sso-server 后端 RequestMapping(/sso/myInfo) public Object myInfo() { // 如果尚未登录 if( ! StpUtil.isLogin()) { return 尚未登录无法获取; } // 原写法直接调用 StpUtil.getLoginId() 当做 centerId 来提交 // Object centerId StpUtil.getLoginId(); // 新写法获取本地 loginId 对应的认证中心 centerId Object centerId SaSsoClientUtil.getSsoTemplate().strategy.convertLoginIdToCenterId.run(StpUtil.getLoginId()); // 推送消息 SaSsoMessage message new SaSsoMessage(); message.setType(userinfo); message.set(loginId, centerId); SaResult result SaSsoClientUtil.pushMessageAsSaResult(message); // 返回给前端 return result; }这样/sso/pushS接口收到的 loginId 就是认证中心视角的 userIdsso-server 端无论做用户资料查询还是单点注销都不会再出现「本地 id 在认证中心查不到」的问题。消息推送的消息类型定义与自定义处理器写法可进一步参考 SSO 消息推送机制 文档单点注销的完整链路可参考 SSO 单点注销。九、三种方案落地建议与参考实现最后做一次总结性对比便于你在真实项目中快速决策维度方案一 统一迁移方案二 实时同步方案三 字段关联是否保留 client 端 user 数据否是是同步强度—强同步弱同步 / 不同步连表查 user 信息麻烦方便方便各自维护开发成本低高中核心实现点一次性数据迁移server 端消息推送 client 端接收落库checkTicketAppendDataticketResultHandle id 转换策略如果你希望直接查看可运行的完整示例仓库提供了模式三的 Demosa-token-demo-sso3-client其中包含了 sso-client 的 ticket 校验结果处理、登录、注销等完整配置代码sso-server 端的示例可参考 sa-token-demo-sso-server 与 SSO 服务端对接文档。需要再次强调的是本文中的三种方案属于架构设计参考并非强制规范。真实场景中每个公司的架构设计千差万别业务上需要连表查询 user 信息就优先考虑方案二/方案三业务简单、不依赖 user 资料展示就选方案一而不需要强同步的系统用字段关联法方案三往往是最低成本的落地方案——它既不需要复杂的同步工程又能让各子系统保持各自的数据自治。【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架让鉴权变得简单、优雅—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考