新增用户数据同步/迁移方案的建议

This commit is contained in:
click33 2024-05-08 17:29:04 +08:00
parent b23aa55ffa
commit 55821d09c6
4 changed files with 238 additions and 1 deletions

View File

@ -147,7 +147,7 @@ Sa-Token SSO 分为三种模式解决同域、跨域、共享Redis、跨Redis
6. 提供前后端分离整合方案:无论是 sso-server 还是 sso-client 的前后端分离都可以整合。
7. 提供安全校验域名校验、ticket校验、参数签名校验有效防 ticket 劫持,防请求重放等攻击。
8. 参数防丢笔者曾试验多个SSO框架均有参数丢失情况比如登录前是`http://a.com?id=1&name=2`,登录成功后就变成了:`http://a.com?id=1`Sa-Token-SSO 内有专门算法保证了参数不丢失,登录成功后精准原路返回。
9. 提供用户数据迁移方案的建议:开发前统一迁移、运行时实时数据同步、根据关键字段匹配、根据 center_id 字段匹配
9. 提供用户数据同步/迁移方案的建议:开发前统一迁移、运行时实时数据同步、根据关联字段匹配、根据 center_id 字段匹配等
10. 提供直接可运行的 demo 示例,帮助你快速熟悉 SSO 大致登录流程。

View File

@ -47,6 +47,7 @@
- [前后端分离下的整合方案](/sso/sso-h5)
- [平台中心跳转模式](/sso/sso-home-jump)
- [不同 Client 不同秘钥](/sso/sso-diff-key)
- [用户数据同步 / 迁移](/sso/user-data-sync)
- [常见问题总结](/sso/sso-questions)
- [Sa-Sso-Pro单点登录商业版](/sso/sso-pro)

View File

@ -31,6 +31,8 @@
- 重构:匿名 client 将不再能解析出所有应用的 ticket。**[不向下兼容]**
- 新增:新增 homeRoute 配置项:在 /sso/auth 登录后不指定 redirect 参数的情况下默认跳转的路由。
- 优化优化登录有效期策略SSO Client 端登录时将延续 SSO Server 端的会话剩余有效期。
- 新增:新增 `checkTicketAppendData` 策略函数,用于在校验 ticket 后,给 sso-client 端追加返回信息。
- 新增SSO章节文档新增用户数据同步/迁移方案的建议。
- 新增插件/示例:
- 新增:新增插件 sa-token-hutool-timed-cache用于整合 Hutool 缓存插件 TimedCache。 **[重要]**
- 新增:新增 SSM 架构整合 Sa-Token 简单示例。 **[重要]**

View File

@ -0,0 +1,234 @@
# 用户数据同步 / 迁移
本篇文章仅提供架构设计的略微参考,真实场景中每个公司的架构设计都是千差万别的,一套设计理论未必能够适应所有公司的项目。
所以如果你觉着本篇文章的设计理念不能契合你公司的需求,请以你公司的原设计为准。
---
### 数据同步需求
在前面的不同架构 SSO 对接示例中,我们均假设了一个前提:
-- _所有的 sso-client 只负责业务操作,不存储 user 数据user 数据全部来源于 sso-server包括登录认证也都是基于 sso-server 里的 user 账号进行校验操作。_
这种架构比较简洁、清晰,是一种理想化的 SSO 架构模型。
然而更多时候,我们遇到的实际情况是:
-- _公司已经有了 N 多个系统,每个系统都有自己独立的一套账号认证体系,现在老板要让这 N 个毫无关系的系统集成单点登录。_
要完成这种需求,首先你得考虑两个问题:
1. 问题一sso-client 需不需要保留 user 数据。
- sso-client 不涉及 user 信息连表查的业务,就可以不保留 user 信息。
- sso-client 涉及 user 信息连表查业务,就需要在 sso-client 保留 user 数据。
2. 问题二:如果保留的话,是和 sso-server 强同步,还是弱同步。
- 强同步就是指 sso-client 的 user 数据和 sso-server 的 user 数据字段值必须保持一致。比如说一个用户在server端昵称修改为“张三”那么在 client 端也要实时同步修改。
- 弱同步就是指两边可以各改各的。比如说:一个用户 server 端修改了昵称为 “张三”,他在 client 端依然可以昵称为 “李四”。
由此可大致分为三种设计方案:
| 方案序号 | 方案名称 | 简单说明 | 适用系统 |
| :-------- | :-------- | :-------- | :-------- |
| 方案一 | 统一迁移 | 统一把用户数据迁移到 sso-server 认证中心再进行对接 | 比较简单的系统,业务上不需要 user 信息连表查 |
| 方案二 | 实时同步 | 按照一定的规则,使 sso-client 和 sso-server 保持 user 信息实时同步 | 一般业务上需要 user 信息连表查的系统 |
| 方案三 | 字段关联 | 不同步,但找一个关键字段,将 sso-client 和 sso-server 的 user 账号进行关联起来 | sso-client 不打算过分依赖 sso-server 的 user 数据,只是想借助 sso-server 完成一下统一登录 |
下面逐一拆解三种方案具体实现。
### 1、方案一统一迁移
对接工作开发前sso-client 的 user 数据完全迁移到 sso-server 中,且自身不再保留 user 数据,只进行业务数据处理操作。
这种方案其实不必过多讲解因为数据完成迁移后整个架构就转化为了上述的“理想化SSO模型”后续对接也比较方便。
迁移方式可以选择数据库同步工具,或者手写代码从 sso-client 库读取数据然后 insert 到 sso-server 库中。
这并非此文探讨的重点,因此不再过多赘述了。
- 方案优点架构简洁明了SSO 登录、注销对接起来非常方便
- 方案缺点sso-client 不存储 user 信息,因此业务上需要连表查询 user 信息的地方会比较麻烦(例如:拉取帖子列表时需要附加显示用户头像和昵称信息)
方案适用范围:适合业务比较简单,不涉及 user资料连表查业务 的子系统。
### 2、方案二实时同步
首先,对接前,数据还是要迁移的,只不过迁移后 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 信息同步。
- 方案优点sso-client 存储了 user 信息,可以比较方便的进行 user 连表查操作。
- 方案缺点sso-server 与 sso-client 的 user 数据同步功能不算简单,开发起来可能要耗费一段不小的工期。
方案适用范围:一般业务上需要 user 信息连表查的子系统都适合。
### 3、方案三字段关联
如果子系统不需要和 sso-server 做到信息强同步,可以使用字段关联法做到账户关联进行登录。
举个例子:公司有三个子系统,电商、论坛、短视频。同一个用户可以在这三个子系统以及 sso-server 认证中心拥有不同的昵称、头像等信息,互不干扰。
例如,在 sso-server 认证中心里,张三的数据库信息为:
| id | username | avatar | password | age | email |
| :-------- | :-------- | :-------- | :-------- | :-------- | :-------- |
| 10001 | ... | ... | ... | ... | ... |
| 10002 | 小明 | cat.jpg | 123456 | 18 | `23397@xx.com` |
| 10003 | ... | ... | ... | ... | ... |
在电商系统里中,张三的数据库信息为:
| id | name | avatar | money | email |
| :-------- | :-------- | :-------- | :-------- | :-------- |
| 100334 | ... | ... | ... | ... |
| 100335 | 二明 | dog.jpg | 1000 | `23397@xx.com` |
| 100336 | ... | ... | ... | ... |
这里的关键点在于,虽然用户 “张三” 在每个系统里的资料都是不同的,但是程序要想办法将它们识别为同一个用户,
要做到这一点,就需要我们准备一个关键字段将信息打通串联起来。例如表中的 “邮箱” 信息可以作为这个“关联字段”。
(注:此处仅展示使用邮箱作为关联字段的操作,实际上除了邮箱以外,手机号、身份证号等具有唯一性的信息都可以作为关联字段)
首先,在 sso-server 端,我们需要重写一下 `checkTicketAppendData` 函数,使其在 “校验 ticket 返回 loginId” 时,追加返回 email 字段。
``` java
// 配置SSO相关参数
@Autowired
private void configSso(SaSsoServerConfig ssoServer) {
// 其它配置 ...
// 配置Ticket校验函数
ssoServer.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;
};
}
```
在 sso-client 端,重写 ticketResultHandle 函数,根据 sso-server 返回的信息查询本地 user 信息并登录:
``` java
// 配置SSO相关参数
@Autowired
private void configSso(SaSsoClientConfig ssoClient) {
// 其它配置 ...
// 自定义校验 ticket 返回值的处理逻辑 (每次从认证中心获取校验 ticket 的结果后调用)
ssoClient.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);
};
}
```
至此完毕。
- 方案优点:
- 1、sso-client 不需要和 sso-server 保持信息强同步,实现起来不复杂,架构也比较清晰易维护。
- 2、同一个用户的信息sso-client 可以和 sso-client 保持不同,各自维护各自的,互不干扰。
- 方案缺点好像没啥缺点除非你觉着上述的第2条优点属于缺点。
方案适用范围:在 user 信息方面不打算过分依赖 sso-server 的系统,希望自己维护自己的 user 信息,只是想借助 sso-server 完成一下统一登录。
### 4、扩展没有关联字段
如果我们的子系统 user 表没有邮箱、手机号等唯一性字段和 sso-server 的 user 表进行关联,该怎么办呢?
没有字段,那就创造个字段,例如:
| id | name | avatar | age | center_id |
| :-------- | :-------- | :-------- | :-------- | :-------- |
| 205421 | ... | ... | ... | ... |
| 205422 | 小风筝 | dog.jpg | 21 | 10002 |
| 205423 | ... | ... | ... | ... |
如上表所示,我们可以在子系统的 user 表新增一列 `center_id`记录这个用户在认证中心所属的账号id。然后在登录时根据这个 `center_id` 来查找相应的用户。
由于 sso-server 端默认就是会返回 loginId 参数的,因此在 sso-server 端不必再重写一下 `checkTicketAppendData` 函数来追加返回信息了,
我们只需要重写 sso-client 端的 `ticketResultHandle` 函数即可:
``` java
// 配置SSO相关参数
@Autowired
private void configSso(SaSsoClientConfig ssoClient) {
// 其它配置 ...
// 自定义校验 ticket 返回值的处理逻辑 (每次从认证中心获取校验 ticket 的结果后调用)
ssoClient.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);
}
// 进行登录
StpUtil.login(user.getId(), ctr.remainSessionTimeout);
StpUtil.getSession().set("user", user);
// 一切工作完毕,重定向回 back 页面
return SaHolder.getResponse().redirect(back);
};
}
```
至此完毕。