diff --git a/sa-token-doc/_sidebar.md b/sa-token-doc/_sidebar.md
index e89bd6b0..a934e21c 100644
--- a/sa-token-doc/_sidebar.md
+++ b/sa-token-doc/_sidebar.md
@@ -45,11 +45,15 @@
- [配置域名校验](/sso/sso-check-domain)
- [定制化登录页面](/sso/sso-custom-login)
- [自定义API路由](/sso/sso-custom-api)
- - [前后端分离下的整合方案](/sso/sso-h5)
- - [NoSdk 模式与非 java 项目](/sso/sso-nosdk)
- [平台中心跳转模式](/sso/sso-home-jump)
- - [不同 Client 不同秘钥](/sso/sso-diff-key)
+ - [匿名 client 接入](/sso/anon-client)
+ - [单点注销](/sso/signout)
+ - [前后端分离下的整合方案](/sso/sso-h5)
+ - [消息推送机制](/sso/message-push)
+
- [用户数据同步 / 迁移](/sso/user-data-sync)
+ - [NoSdk、ReSdk 模式与非 java 项目](/sso/sso-nosdk)
+ - [SSO 代码 API 参考](/sso/sso-dev)
- [常见问题总结](/sso/sso-questions)
- [Sa-Sso-Pro:单点登录商业版](/sso/sso-pro)
diff --git a/sa-token-doc/fun/cors-filter.md b/sa-token-doc/fun/cors-filter.md
index 4f158623..9b2ac0ed 100644
--- a/sa-token-doc/fun/cors-filter.md
+++ b/sa-token-doc/fun/cors-filter.md
@@ -2,4 +2,4 @@
- 参考:[https://juejin.cn/post/7247376558367981627](https://juejin.cn/post/7247376558367981627)
+ 参考:[https://juejin.cn/post/7491603065944129590](https://juejin.cn/post/7491603065944129590)
diff --git a/sa-token-doc/sso/anon-client.md b/sa-token-doc/sso/anon-client.md
new file mode 100644
index 00000000..13814e34
--- /dev/null
+++ b/sa-token-doc/sso/anon-client.md
@@ -0,0 +1,108 @@
+# 匿名 Client 接入
+
+匿名 Client 就是指在客户端没有配置 `sso-client` 的应用,没有一个明确的 “Client” 标识名称。
+
+匿名 Client 在一些关键步骤中不会构建 `client` 参数,如:“重定向至认证中心授权地址”、“校验 ticket”、“单点注销” 等。
+
+要想匿名 client 接入,你需要做一些特殊配置。
+
+
+### 1、在 sso-server 端开启匿名 client 接入
+
+开启方式一,通过配置项方式:
+
+
+
+``` yaml
+# Sa-Token 配置
+sa-token:
+ # SSO-Server 配置
+ sso-server:
+ # 是否启用匿名 client (开启匿名 client 后,允许客户端接入时不提交 client 参数)
+ allow-anon-client: true
+ # 所有允许的授权回调地址 (匿名 client 使用)
+ allow-url: "*"
+ # API 接口调用秘钥 (全局默认 + 匿名 client 使用)
+ secret-key: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
+```
+
+``` properties
+# SSO-Server 配置
+# 是否启用匿名 client (开启匿名 client 后,允许客户端接入时不提交 client 参数)
+sa-token.sso-server.allow-anon-client=true
+# 所有允许的授权回调地址 (匿名 client 使用)
+sa-token.sso-server.allow-url=*
+# API 接口调用秘钥 (全局默认 + 匿名 client 使用)
+sa-token.sso-server.secret-key=kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
+```
+
+
+
+开启方式二,通过代码重写方式:
+
+``` java
+/**
+ * 重写 SaSsoServerTemplate 部分方法,增强功能
+ */
+@Component
+public class CustomSaSsoServerTemplate extends SaSsoServerTemplate {
+
+ /**
+ * 获取配置项:是否允许匿名 client 接入
+ */
+ @Override
+ public boolean getConfigOfAllowAnonClient() {
+ return true;
+ }
+
+ /**
+ * 获取匿名 client 配置信息
+ */
+ @Override
+ public SaSsoClientModel getAnonClient() {
+ SaSsoClientModel scm = new SaSsoClientModel();
+ scm.setAllowUrl("*"); // 允许的授权地址
+ scm.setIsSlo(true); // 是否允许单点注销
+ scm.setSecretKey("kQwIOrYvnXmSDkwEiFngrKidMcdrgKor"); // 客户端密钥
+ return scm;
+ }
+}
+```
+
+
+### 2、在 sso-server 端开启匿名 client 接入
+
+然后在对应的应用端不要配置 client 字段,例如:
+
+``` yml
+# sa-token配置
+sa-token:
+ # 配置一个不同的 token-name,以避免在和模式三 demo 一起测试时发生数据覆盖
+ token-name: satoken-client-anon
+ # sso-client 相关配置
+ sso-client:
+ # client 标识 匿名应用就是指不配置 client 标识的应用
+ # client: sso-client3
+ # sso-server 端主机地址
+ server-url: http://sa-sso-server.com:9000
+ # 使用 Http 请求校验ticket (模式三)
+ is-http: true
+ # 是否在登录时注册单点登录回调接口 (匿名应用想要参与单点注销必须打开这个)
+ reg-logout-call: true
+ # API 接口调用秘钥
+ secret-key: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
+```
+
+
+> [!TIP| label:demo]
+> 匿名 Client 接入的 Demo 示例地址:[sa-token-demo-sso3-client-anon](https://gitee.com/dromara/sa-token/tree/master/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-anon)
+
+这里有个值得注意的配置项:`reg-logout-call: true`,是干嘛的?
+
+简单来讲,就是匿名应用不包含 client 字段信息,因此 sso-server 端也无法配置此 client 的消息推送地址,所以此 client 无法接受到消息推送,也就无法参与到单点注销的环路中来。
+
+因此,新增一个配置项 `reg-logout-call: true`,代表在登录的同时把当前项目的单点注销回调地址 `/sso/logoutCall` 发送到 sso-server 端,
+这样 sso-server 端有了备案,也就可以成功通知此应用发起单点注销掉了。
+
+如果当前应用不需要单点注销可以不配置此字段。
+
diff --git a/sa-token-doc/sso/message-push.md b/sa-token-doc/sso/message-push.md
new file mode 100644
index 00000000..70840e6c
--- /dev/null
+++ b/sa-token-doc/sso/message-push.md
@@ -0,0 +1,107 @@
+# 消息推送机制
+
+消息推送机制简单来讲就是:sso-client 端按照特点格式构建一个 http 请求,调用 sso-server 端的 `/sso/pushS` 接口,sso-server 接收到消息后做出处理回应 sso-client 端。
+
+消息推送是相互的,sso-server 端也可以构建 http 请求,调用 sso-client 端的 `/sso/pushC` 接口。
+
+消息推送机制是应用与认证中心相互沟通的桥梁,ticket 校验、单点注销等行为都是依赖消息推送机制来实现的。
+
+当然你也可以通过自定义消息处理器的方式,来扩展消息推送能力,这将非常有助于你完成一些应用与认证中心的自定义数据交互。
+
+假设我们现在有如下需求:在 sso-client 获取 sso-server 端指定账号 id 的昵称、头像等信息,即:用户资料的拉取。
+
+
+### 1、认证中心自定义消息处理器
+
+首先,我们需要在 sso-server 实现一个消息处理器:
+
+``` java
+@RestController
+public class SsoServerController {
+
+ // 配置SSO相关参数
+ @Autowired
+ private void configSso(SaSsoServerTemplate ssoServerTemplate) {
+
+ // 添加消息处理器:userinfo (获取用户资料) (用于为 client 端开放拉取数据的接口)
+ ssoServerTemplate.messageHolder.addHandle("userinfo", (ssoTemplate, message) -> {
+ System.out.println("收到消息:" + message);
+
+ // 自定义返回结果(模拟)
+ return SaResult.ok()
+ .set("id", message.get("loginId"))
+ .set("name", "LinXiaoYu")
+ .set("sex", "女")
+ .set("age", 18);
+ });
+
+ }
+
+}
+```
+
+
+### 2、应用端调用消息推送接口获取数据
+
+首先保证在配置文件里要配置上消息推送的具体地址
+
+
+
+``` yaml
+# sa-token配置
+sa-token:
+ # sso-client 相关配置
+ sso-client:
+ # 应用标识
+ client: sso-client3
+ # sso-server 端主机地址
+ server-url: http://sa-sso-server.com:9000
+ # sso-server 端推送消息地址
+ # 配置 server-url 后,框架可自动计算对应的 push-url 地址,也可以单独配置 push-url 地址,两者选其一即可
+ # push-url: http://sa-sso-server.com:9000/sso/pushS
+ # API 接口调用秘钥
+ secret-key: SSO-C3-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
+```
+
+``` properties
+# sso-client 相关配置
+# 应用标识
+sa-token.sso-client.client=sso-client3
+# sso-server 端主机地址
+sa-token.sso-client.server-url=http://sa-sso-server.com:9000
+# sso-server 端推送消息地址
+# 配置 server-url 后,框架可自动计算对应的 push-url 地址,也可以单独配置 push-url 地址,两者选其一即可
+sa-token.sso-client.push-url=http://sa-sso-server.com:9000/sso/pushS
+# API 接口调用秘钥
+sa-token.sso-client.secret-key=SSO-C3-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
+```
+
+
+
+然后在需要拉取资料的地方:
+
+``` java
+// 查询我的账号信息:sso-client 前端 -> sso-center 后端 -> sso-server 后端
+@RequestMapping("/sso/myInfo")
+public Object myInfo() {
+ // 如果尚未登录
+ if( ! StpUtil.isLogin()) {
+ return "尚未登录,无法获取";
+ }
+
+ // 获取本地 loginId
+ Object loginId = StpUtil.getLoginId();
+
+ // 构建消息对象
+ SaSsoMessage message = new SaSsoMessage();
+ message.setType("userinfo");
+ message.set("loginId", loginId);
+
+ // 推送至 sso-server,并接收响应数据
+ SaResult result = SaSsoClientUtil.pushMessageAsSaResult(message);
+
+ // 返回给前端
+ return result;
+}
+```
+
diff --git a/sa-token-doc/sso/signout.md b/sa-token-doc/sso/signout.md
new file mode 100644
index 00000000..09fa1e38
--- /dev/null
+++ b/sa-token-doc/sso/signout.md
@@ -0,0 +1,158 @@
+# 单点注销
+
+Sa-Token SSO 提供多种注销模式:
+
+从注销范围上可以分为:
+
+- 单端注销:会话只在当前应用注销,其它应用和认证中心不受影响。
+- 全端注销:一处注销,全端下线。也即:单点注销。
+- 单浏览器注销:该账号的只在当前浏览器登录的应用注销,其它浏览器/设备不受影响。
+
+从注销方式上可以分为:
+- ajax 无刷单点注销:调用指定的 RestAPI 接口完成注销。
+- 跳页面注销:跳转到指定接口进行注销,注销完成后原路返回或跳转到指定页面。
+
+---
+
+### 1、单端注销
+
+在后端添加接口:
+``` java
+// 当前应用独自注销 (不退出其它应用)
+@RequestMapping("/sso/logoutByAlone")
+public Object logoutByAlone() {
+ StpUtil.logout();
+ return SaSsoClientProcessor.instance._ssoLogoutBack(SaHolder.getRequest(), SaHolder.getResponse());
+}
+```
+
+在前端或跳转或 ajax 异步调用此接口即可。
+
+如果是跳转可指定 back 参数,代表注销成功后跳转的地址,例如:`http://sso-client.com/sso/logoutByAlone?back=https://sa-token.cc`
+
+
+### 2、全端注销
+
+此处先简单看一下 Sa-Token SSO 的单点注销链路过程:
+
+1. sso-client 的前端向 sso-client 的后端发起单点注销请求。(调用 `http://{sso-client}/sso/logout`)
+2. sso-client 的后端向 sso-server 的后端发送单点注销请求。(调用 `http://{sso-server}/sso/pushS?msgType=signout`)
+3. sso-server 端遍历 client 列表,逐个推送消息通知 sso-client 端下线。(`http://{sso-client}/sso/pushC?msgType=logoutCall`)
+4. sso-server 端注销下线。
+5. sso-server 后端响应 sso-client 后端:注销完成。
+6. sso-client 后端响应 sso-client 前端:注销完成。
+7. 整体完成。
+
+
+
+
+
+这些逻辑 Sa-Token 内部已经封装完毕,你只需按照文档步骤集成即可。以模式三 demo 为例:
+
+#### 2.1、更改注销方案
+
+单点注销是 Sa-Token SSO 内部已封装的接口,无需手动再添加,只需要在前端调用即可。
+
+``` java
+// SSO-Client端:首页
+@RequestMapping("/")
+public String index() {
+ String str = "
Sa-Token SSO-Client 应用端 (模式三)
" +
+ "当前会话是否登录:" + StpUtil.isLogin() + " (" + StpUtil.getLoginId("") + ")
" +
+ " " +
+ "登录 - " +
+ "单应用注销 - " +
+ "全端注销 " +
+ "
";
+ return str;
+}
+```
+
+重点在第 9 行。
+
+#### 2.2、启动测试
+重启项目,依次登录三个 client:
+- [http://sa-sso-client1.com:9003/](http://sa-sso-client1.com:9003/)
+- [http://sa-sso-client2.com:9003/](http://sa-sso-client2.com:9003/)
+- [http://sa-sso-client3.com:9003/](http://sa-sso-client3.com:9003/)
+
+
+
+在任意一个 client 里,点击 **`[注销]`** 按钮,即可单点注销成功(打开另外两个client,刷新一下页面,登录态丢失)。
+
+
+
+
+
+PS:这里我们为了方便演示,使用的是超链接跳页面的形式,正式项目中使用 Ajax 调用接口即可做到无刷单点登录退出。
+
+例如,我们使用 [Apifox 接口测试工具](https://www.apifox.cn/) 可以做到同样的效果:
+
+
+
+
+
+### 3、单浏览器注销
+
+单浏览器注销的前提是在登录时按照 `deviceId` 设备ID 参数为登录进行分组,这样在发起注销时即可格局设备ID参数做到单浏览器注销功能。
+
+#### 3.1、sso-server 端加上设备ID参数登录
+
+首先在 sso-server 的登录方法内,加上 deviceId 参数,例如:
+
+``` java
+@RestController
+public class SsoServerController {
+
+ // 其它代码,非重点,省略展示...
+
+ // 配置SSO相关参数
+ @Autowired
+ private void configSso(SaSsoServerTemplate ssoServerTemplate) {
+ // 配置:登录处理函数
+ ssoServerTemplate.strategy.doLoginHandle = (name, pwd) -> {
+ // 此处仅做模拟登录,真实环境应该查询数据库进行登录
+ if("sa".equals(name) && "123456".equals(pwd)) {
+ String deviceId = SaHolder.getRequest().getParam("deviceId", SaFoxUtil.getRandomString(32));
+ StpUtil.login(10001, new SaLoginParameter().setDeviceId(deviceId));
+ return SaResult.ok("登录成功!").setData(StpUtil.getTokenValue());
+ }
+ return SaResult.error("登录失败!");
+ };
+
+ }
+}
+```
+
+如上代码,在登录时获取前端提交的 deviceId 参数,如果前端没有提交则随机生成一个。
+
+
+#### 3.2、sso-client 端发起注销时指定单设备注销参数
+
+然后在 sso-client 发起单点注销时,加上 `singleDeviceIdLogout=true` 参数,代表按照设备 id 进行分组注销,非本设备id的会话不参与注销行为:
+
+``` java
+// SSO-Client端:首页
+@RequestMapping("/")
+public String index() {
+ String str = "Sa-Token SSO-Client 应用端 (模式三)
" +
+ "当前会话是否登录:" + StpUtil.isLogin() + " (" + StpUtil.getLoginId("") + ")
" +
+ " " +
+ "登录 - " +
+ "单应用注销 - " +
+ "单浏览器注销 - " +
+ "全端注销 " +
+ "
";
+ return str;
+}
+```
+
+重点在第 9 行。
+
+
+> [!WARNING| label:测试注意点]
+> 在进行测试时,同时将一个浏览器双击打开两次,是不算 “不同浏览器” 的,虽然你打开了两个浏览器窗口,但是这两个浏览器的会话数据是互通的。
+>
+> 必须打开两个不同的浏览器来测试,或者按快捷键 `ctrl + shift + N` 打开隐私模式,才可以做到会话相互隔离。
+
+
diff --git a/sa-token-doc/sso/sso-apidoc.md b/sa-token-doc/sso/sso-apidoc.md
index 093a6e4f..1926ca9e 100644
--- a/sa-token-doc/sso/sso-apidoc.md
+++ b/sa-token-doc/sso/sso-apidoc.md
@@ -20,7 +20,7 @@ http://{host}:{port}/sso/auth
| 参数 | 是否必填 | 说明 |
| :-------- | :-------- | :-------- |
-| redirect | 是 | 登录成功后的重定向地址,一般填写 location.href(从哪来回哪去) |
+| redirect | 否 | 登录成功后的重定向地址,一般填写 location.href(从哪来回哪去),如不填,则跳转至 home-route |
| mode | 否 | 授权模式,取值 [simple, ticket],simple=登录后直接重定向,ticket=带着ticket参数重定向,默认值为ticket |
| client | 否 | 客户端标识,可不填,代表是一个匿名应用,若填写了,则校验 ticket 时也必须是这个 client 才可以校验成功 |
@@ -28,12 +28,13 @@ http://{host}:{port}/sso/auth
- 情况一:当前会话在 SSO 认证中心未登录,会进入登录页开始登录。
- 情况二:当前会话在 SSO 认证中心已登录,会被重定向至 `redirect` 地址,并携带 `ticket` 参数。
-`ticket` 码具有以下特点:
+Ticket 码具有以下特点:
1. 每次授权产生的 `ticket` 码都不一样。
2. `ticket` 码用完即废,不能二次使用。
3. 一个 `ticket` 的有效期默认为五分钟,超时自动作废。
4. 每次授权产生新 `ticket` 码,会导致旧 `ticket` 码立即作废,即使旧 `ticket` 码尚未使用。
+
### 2、RestAPI 登录接口
``` url
http://{host}:{port}/sso/doLogin
@@ -46,42 +47,63 @@ http://{host}:{port}/sso/doLogin
| name | 是 | 用户名 |
| pwd | 是 | 密码 |
-此接口属于 RestAPI (使用ajax访问),会进入后端配置的 `ssoServer.doLoginHandle` 函数中,此函数的返回值即是此接口的响应值。
+此接口属于 RestAPI (使用ajax访问),会进入后端配置的 `ssoServerTemplate.strategy.doLoginHandle` 函数中,此函数的返回值即是此接口的响应值。
另外需要注意:此接口并非只能携带 name、pwd 参数,因为你可以在方法里通过 `SaHolder.getRequest().getParam("xxx")` 来获取前端提交的其它参数。
-### 3、Ticket 校验接口
-此接口仅配置模式三 `(isHttp=true)` 时打开
+### 3、单点注销接口
+``` url
+http://{host}:{port}/sso/signout
+```
+
+接受参数:
+
+| 参数 | 是否必填 | 说明 |
+| :-------- | :-------- | :-------- |
+| back | 否 | 注销成功后的重定向地址,一般填写 location.href(从哪来回哪去),也可以填写 self 字符串,含义同上 |
+
+
+### 4、消息推送接口
``` url
-http://{host}:{port}/sso/checkTicket
+http://{host}:{port}/sso/pusS
```
接收参数:
| 参数 | 是否必填 | 说明 |
| :-------- | :-------- | :-------- |
-| ticket | 是 | 在步骤 1 中授权重定向时的 ticket 参数 |
-| ssoLogoutCall | 否 | 单点注销时的回调通知地址,只在SSO模式三单点注销时需要携带此参数|
-| client | 否 | 客户端标识,可不填,代表是一个匿名应用,若填写了,则必须填写的和 `/sso/auth` 登录时填写的一致才可以校验成功 |
+| client | 否 | 客户端标识,可不填,代表是一个匿名应用 |
| timestamp | 是 | 当前时间戳,13位 |
| nonce | 是 | 随机字符串 |
-| sign | 是 | 签名,生成算法:`md5( [client={client值}&]nonce={随机字符串}&[ssoLogoutCall={单点注销回调地址}&]ticket={ticket值}×tamp={13位时间戳}&key={secretkey秘钥} )` 注:[]内容代表可选 |
+| sign | 是 | 签名,生成算法:`md5( client={client值}&nonce={随机字符串}×tamp={13位时间戳}&key={secretkey秘钥} )` |
-返回值场景:
-- 校验成功时:
+此接口可根据消息类型增加任意参数。新增加的参数要参与 sign 签名。
+
+返回值示例:
+
+- 推送成功时:
``` js
{
"code": 200,
"msg": "ok",
- "data": "10001", // 此 ticket 指向的 loginId
- "remainSessionTimeout": 7200, // 此账号在 sso-server 端的会话剩余有效期(单位:s)
+ "data": "10001", // 返回的数据
}
```
-- 校验失败时:
+- 推送失败时:
+
+``` js
+{
+ "code": 500, // 200表示请求成功,非200标识请求失败
+ "msg": "签名无效:xxx", // 失败原因
+ "data": null
+}
+```
+
+- 也有可能消息推送成功了,但是处理消息失败,例如校验 ticket 时:
``` js
{
@@ -92,58 +114,6 @@ http://{host}:{port}/sso/checkTicket
```
-### 4、单点注销接口
-``` url
-http://{host}:{port}/sso/signout
-```
-
-此接口有两种调用方式
-
-##### 4.1、方式一:在 Client 的前端页面引导用户直接跳转,并带有 back 参数
-例如:
-
-``` url
-http://{host}:{port}/sso/signout?back=xxx
-```
-用户注销成功后将返回 back 地址
-
-##### 4.2、方式二:在 Client 的后端通过 http 工具来调用
-
-接受参数:
-
-| 参数 | 是否必填 | 说明 |
-| :-------- | :-------- | :-------- |
-| loginId | 是 | 要注销的账号 id |
-| timestamp | 是 | 当前时间戳,13位 |
-| nonce | 是 | 随机字符串 |
-| sign | 是 | 签名,生成算法:`md5( loginId={账号id}&nonce={随机字符串}×tamp={13位时间戳}&key={secretkey秘钥} )` |
-| client | 否 | 客户端标识,可不填,一般在帮助 “sso-server 端不同client不同秘钥” 的场景下找到对应秘钥时,才填写 |
-
-例如:
-``` url
-http://{host}:{port}/sso/signout?loginId={value}×tamp={value}&nonce={value}&sign={value}
-```
-
-将返回 json 数据结果,形如:
-
-``` js
-{
- "code": 200, // 200表示请求成功,非200标识请求失败
- "msg": "单点注销成功",
- "data": null
-}
-```
-
-如果单点注销失败,将返回:
-
-``` js
-{
- "code": 500, // 200表示请求成功,非200标识请求失败
- "msg": "签名无效:xxx", // 失败原因
- "data": null
-}
-```
-
@@ -197,7 +167,7 @@ http://{host}:{port}/sso/logout
### 3、单点注销回调接口
-此接口仅配置模式三 `(isHttp=true)` 时打开,且为框架回调,开发者无需关心
+此接口仅配置 `(reg-logout-call=true)` 时打开,且为框架回调,开发者无需关心
``` url
http://{host}:{port}/sso/logoutCall
@@ -227,6 +197,45 @@ http://{host}:{port}/sso/logoutCall
+### 4、消息推送接口
+
+``` url
+http://{host}:{port}/sso/pusC
+```
+
+接收参数:
+
+| 参数 | 是否必填 | 说明 |
+| :-------- | :-------- | :-------- |
+| timestamp | 是 | 当前时间戳,13位 |
+| nonce | 是 | 随机字符串 |
+| sign | 是 | 签名,生成算法:`md5( nonce={随机字符串}×tamp={13位时间戳}&key={secretkey秘钥} )` |
+
+此接口可根据消息类型增加任意参数。新增加的参数要参与 sign 签名。
+
+返回值示例:
+
+- 推送成功时:
+
+``` js
+{
+ "code": 200,
+ "msg": "ok",
+ "data": "10001", // 返回的数据
+}
+```
+
+- 推送失败时:
+
+``` js
+{
+ "code": 500, // 200表示请求成功,非200标识请求失败
+ "msg": "签名无效:xxx", // 失败原因
+ "data": null
+}
+```
+
+
diff --git a/sa-token-doc/sso/sso-check-domain.md b/sa-token-doc/sso/sso-check-domain.md
index 1af4ea02..12992e3c 100644
--- a/sa-token-doc/sso/sso-check-domain.md
+++ b/sa-token-doc/sso/sso-check-domain.md
@@ -3,19 +3,19 @@
---
### 1、Ticket劫持攻击
-在前面章节的 SSO-Server 示例中,配置项 `sa-token.sso-server.allow-url=*` 意为配置所有允许的Client端授权地址,不在此配置项中的URL将无法单点登录成功
+在前面章节的 SSO-Server 示例中,配置项 `sa-token.sso-server.clients.sso-client3.allow-url=*` 意为改 client 所有允许的授权地址,不在此配置项中的 URL 将无法单点登录成功。
-为了方便测试,上述代码将其配置为`*`,但是,在生产环境中,此配置项绝对不能配置为 * ,否则会有被 Ticket 劫持的风险
+为了方便测试,上述代码将其配置为`*`,但是,在生产环境中,此配置项绝对不能配置为 * ,否则会有被 Ticket 劫持的风险。
-假设攻击者根据模仿我们的授权地址,巧妙的构造一个URL
+假设攻击者根据模仿我们的授权地址,巧妙的构造一个URL:
-> [http://sa-sso-server.com:9000/sso/auth?redirect=https://www.baidu.com/](http://sa-sso-server.com:9000/sso/auth?redirect=https://www.baidu.com/)
+> [http://sa-sso-server.com:9000/sso/auth?client=sso-client3&redirect=https://www.baidu.com/](http://sa-sso-server.com:9000/sso/auth?client=sso-client3&redirect=https://www.baidu.com/)
-当不知情的小红被诱导访问了这个URL时,它将被重定向至百度首页
+当不知情的小红被诱导访问了这个URL时,它将被重定向至百度首页:

-可以看到,代表着用户身份的 Ticket 码也显现到了URL之中,借此漏洞,攻击者完全可以构建一个URL将小红的 Ticket 码自动提交到攻击者自己的服务器,伪造小红身份登录网站
+可以看到,代表着用户身份的 Ticket 码也显现到了 URL 之中,借此漏洞,攻击者完全可以构建一个URL将小红的 Ticket 码自动提交到攻击者自己的服务器,伪造小红身份登录网站
### 2、防范方法
@@ -27,13 +27,15 @@
``` yaml
sa-token:
sso-server:
- # 配置允许单点登录的 url
- allow-url: http://sa-sso-client1.com:9001/sso/login
+ clients:
+ sso-client3:
+ # 配置允许单点登录的 url
+ allow-url: http://sa-sso-client1.com:9003/sso/login
```
``` properties
# 配置允许单点登录的 url
-sa-token.sso-server.allow-url=http://sa-sso-client1.com:9001/sso/login
+sa-token.sso-server.clients.so-client3.allow-url=http://sa-sso-client1.com:9003/sso/login
```
diff --git a/sa-token-doc/sso/sso-custom-api.md b/sa-token-doc/sso/sso-custom-api.md
index fb734e82..c82fa749 100644
--- a/sa-token-doc/sso/sso-custom-api.md
+++ b/sa-token-doc/sso/sso-custom-api.md
@@ -33,13 +33,11 @@ public class SsoServerController {
``` java
// 配置SSO相关参数
@Autowired
-private void configSso(SaSsoServerConfig ssoServer) {
+private void configSso(SaSsoServerTemplate ssoServerTemplate) {
// 自定义API地址
SaSsoServerProcessor.instance.ssoServerTemplate.apiName.ssoAuth = "/sso/auth2";
// ...
- // SSO 相关配置
- ssoServer.xxx ... ;
}
```
@@ -70,10 +68,10 @@ public class SsoServerController {
return SaSsoServerProcessor.instance.ssoDoLogin();
}
- // SSO-Server:校验ticket 获取账号id
- @RequestMapping("/sso/checkTicket")
- public Object ssoCheckTicket() {
- return SaSsoServerProcessor.instance.ssoCheckTicket();
+ // SSO-Server:接收推送消息地址
+ @RequestMapping("/sso/pushS")
+ public Object ssoPushS() {
+ return SaSsoServerProcessor.instance.ssoPushS();
}
// SSO-Server:单点注销
@@ -89,3 +87,41 @@ public class SsoServerController {
拆分式路由 与 聚合式路由 在功能上完全等价,且提供了更为细致的路由管控。
+
+### SSO-Client 端拆分路由入口示例
+
+``` java
+/**
+ * Sa-Token-SSO Client端 Controller
+ */
+@RestController
+public class SsoClientController {
+
+ // SSO-Client:登录地址
+ @RequestMapping("/sso/login")
+ public Object ssoLogin() {
+ return SaSsoClientProcessor.instance.ssoLogin();
+ }
+
+ // SSO-Client:单点注销地址
+ @RequestMapping("/sso/logout")
+ public Object ssoLogout() {
+ return SaSsoClientProcessor.instance.ssoLogout();
+ }
+
+ // SSO-Client:单点注销回调
+ @RequestMapping("/sso/logoutCall")
+ public Object ssoLogoutCall() {
+ return SaSsoClientProcessor.instance.ssoLogoutCall();
+ }
+
+ // SSO-Client:接收消息推送地址
+ @RequestMapping("/sso/ssoPushC")
+ public Object ssoPushC() {
+ return SaSsoClientProcessor.instance.ssoPushC();
+ }
+
+ // ... 其它方法
+
+}
+```
\ No newline at end of file
diff --git a/sa-token-doc/sso/sso-custom-login.md b/sa-token-doc/sso/sso-custom-login.md
index c01b95a9..dc5dcc0d 100644
--- a/sa-token-doc/sso/sso-custom-login.md
+++ b/sa-token-doc/sso/sso-custom-login.md
@@ -81,7 +81,7 @@ if(res.code == 401) {
``` java
// 配置:未登录时返回的View
-sso.notLoginView = () -> {
+ssoServerTemplate.strategy.notLoginView = () -> {
return new ModelAndView("xxx.html");
}
```
@@ -119,7 +119,7 @@ public SaResult ss(String name, String pwd) {
如果你的不同应用覆盖的用户群体差异极大,此时你可能想针对不同的应用跳转到不同的登录页,让每个应用的用户在登录时能够看到当前应用的专属信息,怎么做呢?
-首先,你需要在每个 sso-client 端配置上不同的 client 标识:
+首先,保证每个 sso-client 端都配置了不同的 client 标识:
@@ -143,10 +143,10 @@ sa-token.sso-client.client=sso-client-shop
``` java
// 配置SSO相关参数
@Autowired
-private void configSso(SaSsoServerConfig ssoServer) {
+private void configSso(SaSsoServerTemplate ssoServerTemplate) {
// 配置:未登录时返回的View
- ssoServer.notLoginView = () -> {
+ ssoServerTemplate.strategy.notLoginView = () -> {
String client = SaHolder.getRequest().getParam("client");
if("sso-client-shop".equals(client)) {
diff --git a/sa-token-doc/sso/sso-dev.md b/sa-token-doc/sso/sso-dev.md
new file mode 100644
index 00000000..d3368bf7
--- /dev/null
+++ b/sa-token-doc/sso/sso-dev.md
@@ -0,0 +1,143 @@
+# Sa-Token SSO Server端 二次开发用到的所有函数说明
+
+本篇展示一下 SSO 模块常用的工具类、方法
+
+## Sso-Server 工具类
+
+### Ticket 操作
+``` java
+// 增删改
+
+// 删除 Ticket
+SaSsoServerUtil.deleteTicket(String ticket);
+
+// 根据参数创建一个 ticket 码,并保存
+SaSsoServerUtil.createTicketAndSave(String client, Object loginId, String tokenValue);
+
+// 查
+
+// 查询 ticket ,如果 ticket 无效则返回 null
+SaSsoServerUtil.getTicket(String ticket);
+
+// 查询 ticket 指向的 loginId,如果 ticket 码无效则返回 null
+SaSsoServerUtil.getLoginId(String ticket);
+
+// 查询 ticket 指向的 loginId,并转换为指定类型
+SaSsoServerUtil.getLoginId(String ticket, Class cs);
+
+// 校验
+
+// 校验 Ticket,无效 ticket 会抛出异常
+SaSsoServerUtil.checkTicket(String ticket);
+
+// 校验 Ticket 码,无效 ticket 会抛出异常,如果此ticket是有效的,则立即删除
+SaSsoServerUtil.checkTicketParamAndDelete(String ticket);
+
+// 校验 Ticket,无效 ticket 会抛出异常,如果此ticket是有效的,则立即删除
+SaSsoServerUtil.checkTicketParamAndDelete(String ticket, String client);
+
+// ticket 索引
+
+// 查询 指定 client、loginId 其所属的 ticket 值
+SaSsoServerUtil.getTicketValue(String client, Object loginId);
+```
+
+
+
+### Client 信息获取
+``` java
+// 获取所有 Client
+SaSsoServerUtil.getClients();
+
+// 获取应用信息,无效 client 返回 null
+SaSsoServerUtil.getClient(String client);
+
+// 获取应用信息,无效 client 则抛出异常
+SaSsoServerUtil.getClientNotNull(String client);
+
+// 获取匿名 client 信息
+SaSsoServerUtil.getAnonClient();
+
+// 获取所有需要接收消息推送的 Client
+SaSsoServerUtil.getNeedPushClients();
+```
+
+
+### 重定向 URL 构建与校验
+``` java
+// 构建 URL:sso-server 端向 sso-client 下放 ticket 的地址
+SaSsoServerUtil.buildRedirectUrl(String client, String redirect, Object loginId, String tokenValue);
+
+// 校验重定向 url 合法性
+SaSsoServerUtil.checkRedirectUrl(String client, String url);
+```
+
+
+### 单点注销
+``` java
+// 指定账号单点注销
+SaSsoServerUtil.ssoLogout(Object loginId);
+
+// 指定账号单点注销
+SaSsoServerUtil.ssoLogout(Object loginId, SaLogoutParameter logoutParameter);
+```
+
+
+
+
+
+### 消息推送
+``` java
+// 向指定 Client 推送消息
+SaSsoServerUtil.pushMessage(SaSsoClientModel clientModel, SaSsoMessage message);
+
+// 向指定 client 推送消息,并将返回值转为 SaResult
+SaSsoServerUtil.pushMessageAsSaResult(SaSsoClientModel clientModel, SaSsoMessage message);
+
+// 向指定 Client 推送消息
+SaSsoServerUtil.pushMessage(String client, SaSsoMessage message);
+
+// 向指定 client 推送消息,并将返回值转为 SaResult
+SaSsoServerUtil.pushMessageAsSaResult(String client, SaSsoMessage message);
+
+// 向所有 Client 推送消息
+SaSsoServerUtil.pushToAllClient(SaSsoMessage message);
+
+// 向所有 Client 推送消息,并忽略掉某个 client
+SaSsoServerUtil.pushToAllClient(SaSsoMessage message, String ignoreClient);
+```
+
+
+详情请参考源码:[码云:SaSsoServerUtil.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoServerUtil.java)
+
+
+
+## Sso-Client 工具类
+
+
+### 构建交互地址
+``` java
+// 构建URL:Server端 单点登录授权地址
+SaSsoClientUtil.buildServerAuthUrl(String clientLoginUrl, String back);
+```
+
+
+### 消息推送
+
+``` java
+// 向 sso-server 推送消息
+SaSsoClientUtil.pushMessage(SaSsoMessage message);
+
+// 向 sso-server 推送消息,并将返回值转为 SaResult
+SaSsoClientUtil.pushMessageAsSaResult(SaSsoMessage message);
+
+// 构建消息:校验 ticket
+SaSsoClientUtil.buildCheckTicketMessage(String ticket, String ssoLogoutCallUrl);
+
+// 构建消息:单点注销
+SaSsoClientUtil.buildSignoutMessage(Object loginId, SaLogoutParameter logoutParameter);
+```
+
+详情请参考源码:[码云:SaSsoClientUtil.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-plugin/sa-token-sso/src/main/java/cn/dev33/satoken/sso/template/SaSsoClientUtil.java)
+
+
diff --git a/sa-token-doc/sso/sso-h5.md b/sa-token-doc/sso/sso-h5.md
index 1aa5d6ba..f0b02187 100644
--- a/sa-token-doc/sso/sso-h5.md
+++ b/sa-token-doc/sso/sso-h5.md
@@ -2,135 +2,321 @@
---
-如果我们已有的系统是前后端分离模式,我们显然不能为了接入SSO而改造系统的基础架构,官方仓库的示例采用的是前后端一体方案,要将其改造为前后端分离架构模式非常简单
+## SSO-Client 前后端分离
-以`sa-token-demo-sso2-client`为例:
+要在前后端分离的环境中接入 SSO,思路不难,主要的工作是吧后端 `/sso/login` 接口的路由中转工作拿到前端来,以`sa-token-demo-sso3-client`为例:
+
+### 1、在 sso-client 后端新建`H5Controller`,开放接口:
-### 1、新建`H5Controller`开放接口
``` java
/**
- * 前后端分离架构下集成SSO所需的接口
+ * 前后台分离架构下集成 SSO 所需的代码 (SSO-Client端)
*/
@RestController
public class H5Controller {
- // 当前是否登录
+ // 判断当前是否登录
@RequestMapping("/sso/isLogin")
public Object isLogin() {
- return SaResult.data(StpUtil.isLogin());
+ return SaResult.data(StpUtil.isLogin()).set("loginId", StpUtil.getLoginIdDefaultNull());
}
// 返回SSO认证中心登录地址
@RequestMapping("/sso/getSsoAuthUrl")
public SaResult getSsoAuthUrl(String clientLoginUrl) {
- String serverAuthUrl = SaSsoUtil.buildServerAuthUrl(clientLoginUrl, "");
+ String serverAuthUrl = SaSsoClientUtil.buildServerAuthUrl(clientLoginUrl, "");
return SaResult.data(serverAuthUrl);
}
- // 根据ticket进行登录
+ // 根据 ticket 进行登录
@RequestMapping("/sso/doLoginByTicket")
public SaResult doLoginByTicket(String ticket) {
- SaCheckTicketResult ctr = SaSsoClientProcessor.instance.checkTicket(ticket, "/sso/doLoginByTicket");
- StpUtil.login(ctr.loginId, ctr.remainSessionTimeout);
+ SaCheckTicketResult ctr = SaSsoClientProcessor.instance.checkTicket(ticket);
+ StpUtil.login(ctr.loginId, new SaLoginParameter()
+ .setTimeout(ctr.remainTokenTimeout)
+ .setDeviceId(ctr.deviceId)
+ );
return SaResult.data(StpUtil.getTokenValue());
}
- // 全局异常拦截
- @ExceptionHandler
- public SaResult handlerException(Exception e) {
- e.printStackTrace();
- return SaResult.error(e.getMessage());
- }
-
}
```
-### 2、增加跨域过滤器`CorsFilter.java`
-源码详见:[CorsFilter.java](https://gitee.com/dromara/sa-token/tree/master/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso2-client/src/main/java/com/pj/h5/CorsFilter.java),
-将其复制到项目中即可
+
+### 2、增加跨域处理策略
+
+``` java
+/**
+ * [Sa-Token 权限认证] 配置类
+ */
+@Configuration
+public class SaTokenConfigure {
+
+ /**
+ * CORS 跨域处理策略
+ */
+ @Bean
+ public SaCorsHandleFunction corsHandle() {
+ return (req, res, sto) -> {
+ res.
+ // 允许指定域访问跨域资源
+ setHeader("Access-Control-Allow-Origin", "*")
+ // 允许所有请求方式
+ .setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE")
+ // 有效时间
+ .setHeader("Access-Control-Max-Age", "3600")
+ // 允许的header参数
+ .setHeader("Access-Control-Allow-Headers", "*");
+
+ // 如果是预检请求,则立即返回到前端
+ SaRouter.match(SaHttpMethod.OPTIONS)
+ .free(r -> System.out.println("--------OPTIONS预检请求,不做处理"))
+ .back();
+ };
+ }
+
+}
+```
+
+详细参考:[解决跨域问题](/fun/cors-filter)
+
### 3、新建前端项目
任意文件夹新建前端项目:`sa-token-demo-sso-client-h5`,在根目录添加测试文件:`index.html`
-``` js
+``` html
- Sa-Token-SSO-Client端-测试页(前后端分离版)
+ Sa-Token-SSO-Client端-测试页(前后端分离版-原生h5)
- Sa-Token SSO-Client 应用端(前后端分离版)
+ Sa-Token SSO-Client 应用端(前后端分离版-原生h5)
当前是否登录:
- 登录
- 注销
+ 登录 -
+ 单应用注销 -
+ 单浏览器注销 -
+ 全端注销 -
+ 账号资料
-
+
```
-### 4、添加登录处理文件`sso-login.html`
-源码详见:[sso-login.html](https://gitee.com/dromara/sa-token/tree/master/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-client-h5/sso-login.html),
-将其复制到项目中即可,与`index.html`一样放在根目录下
+### 4、添加单点登录登录中转页
+
+在根目录创建文件:`sso-login.html`
+
+``` html
+
+
+
+
+ Sa-Token-SSO-Client端-登录中转页页
+
+
+
+
+ 加载中 ...
+
+
+
+
+
+```
-### 5、测试运行
-先启动Server服务端与Client服务端,再随便找个能预览html的工具打开前端项目(比如[HBuilderX](https://www.dcloud.io/hbuilderx.html)),测试流程与一体版一致
+### 5、添加公共 js文件
+新建 `sso-common.js`:
+
+``` js
+// 服务器接口主机地址
+// var baseUrl = "http://sa-sso-client1.com:9002"; // 模式二后端
+var baseUrl = "http://sa-sso-client1.com:9003"; // 模式三后端
+
+// 封装一下Ajax
+function ajax(path, data, successFn, errorFn) {
+ console.log('发起请求:', baseUrl + path, JSON.stringify(data));
+ fetch(baseUrl + path, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'X-Requested-With': 'XMLHttpRequest',
+ 'satoken': localStorage.getItem('satoken')
+ },
+ body: serializeToQueryString(data),
+ })
+ .then(response => response.json())
+ .then(res => {
+ console.log('返回数据:', res);
+ if(res.code === 500) {
+ return alert(res.msg);
+ }
+ successFn(res);
+ })
+ .catch(error => {
+ console.error('请求失败:', error);
+ return alert("异常:" + JSON.stringify(error));
+ });
+}
+
+// ------------ 工具方法 ---------------
+
+// 从url中查询到指定名称的参数值
+function getParam(name, defaultValue) {
+ var query = window.location.search.substring(1);
+ var vars = query.split("&");
+ for (var i = 0; i < vars.length; i++) {
+ var pair = vars[i].split("=");
+ if (pair[0] == name) {
+ return pair[1];
+ }
+ }
+ return (defaultValue == undefined ? null : defaultValue);
+}
+
+// 将 json 对象序列化为kv字符串,形如:name=Joh&age=30&active=true
+function serializeToQueryString(obj) {
+ return Object.entries(obj)
+ .filter(([_, value]) => value != null) // 过滤 null 和 undefined
+ .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
+ .join('&');
+}
+
+// 向指定标签里 set 内容
+function setHtml(select, html) {
+ const dom = document.querySelector('.is-login');
+ if(dom) {
+ dom.innerHTML = html;
+ }
+}
+```
-### 6、SSO-Server 端的前后端分离
-疑问:上述代码都是针对 Client 端进行拆分,如果我想在 SSO-Server 端也进行前后端分离改造,应该怎么做?
+### 6、测试运行
+先启动 Server 服务端与 Client 服务端,再随便找个能预览html的工具打开前端项目(比如[HBuilderX](https://www.dcloud.io/hbuilderx.html)),测试流程与一体版一致,暂不赘述。
-> 答:解决思路都是大同小异的,与Client一样,我们需要把原本在 “后端处理的授权重定向逻辑” 拿到前端来实现。
-由于集成代码与 Client 端类似,这里暂不贴详细代码,我们可以下载官方仓库,里面有搭建好的demo
-使用前端ide导入项目 `/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server-h5`,浏览器访问 `sso-auth.html` 页面:
+
+> [!TIP| label:另附其它技术栈的前后端分离 demo 示例:]
+> - sso-client 前后端分离 - 原生h5:[源码链接](https://gitee.com/dromara/sa-token/tree/master/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-client-h5)
+> - sso-client 前后端分离 - vue2:[源码链接](https://gitee.com/dromara/sa-token/tree/master/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-client-vue2)
+> - sso-client 前后端分离 - vue3:[源码链接](https://gitee.com/dromara/sa-token/tree/master/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-client-vue3)
+
+
+## SSO-Server 前后端分离
+
+解决思路与 SSO-Client 一样,我们需要把原本在 “后端处理的授权重定向逻辑” 拿到前端来实现。
+
+由于集成代码与 Client 端类似,这里暂不贴详细代码,我们可以下载官方仓库,里面有搭建好的demo。
+
+使用前端 ide 导入项目 `/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server-h5`,浏览器访问 `sso-auth.html` 页面:

-复制上述地址,将其配置到 Client 端的 yml 配置文件中,例如:
+复制上述地址,将其配置到 Client 端的配置项 `sa-token.sso-client.auth-url` ,例如:
``` yaml
sa-token:
sso-client:
- # SSO-Server端 统一认证地址
+ # sso-server 端主机地址
+ server-url: http://sa-sso-server.com:9000
+ # 在 sso-server 端前后端分离时需要单独配置 auth-url 参数(上面的不要注释,auth-url 配置项和 server-url 要同时存在)
auth-url: http://127.0.0.1:8848/sa-token-demo-sso-server-h5/sso-auth.html
```
``` properties
# SSO-Server端 统一认证地址
+sa-token.sso-client.server-url=http://sa-sso-server.com:9000
+# 在 sso-server 端前后端分离时需要单独配置 auth-url 参数(上面的不要注释,auth-url 配置项和 server-url 要同时存在)
sa-token.sso-client.auth-url=http://127.0.0.1:8848/sa-token-demo-sso-server-h5/sso-auth.html
```
-然后我们启动项目 `sa-token-demo-sso-server` 与 `sa-token-demo-sso2-client`,按照之前的测试步骤访问:
-[http://sa-sso-client1.com:9001/](http://sa-sso-client1.com:9001/),即可以前后端分离模式完成 SSO-Server 端的授权登录。
+然后我们启动项目 sso-server 与 sso-client ,按照之前的测试步骤访问:
+[http://sa-sso-client1.com:9003/](http://sa-sso-client1.com:9003/),即可以前后端分离模式完成 SSO-Server 端的授权登录。
diff --git a/sa-token-doc/sso/sso-home-jump.md b/sa-token-doc/sso/sso-home-jump.md
index 87b24cc3..a7f1b7f4 100644
--- a/sa-token-doc/sso/sso-home-jump.md
+++ b/sa-token-doc/sso/sso-home-jump.md
@@ -15,22 +15,43 @@
假设子系统的地址是:
``` url
-http://sa-sso-client1.com:9001/
+http://sa-sso-client1.com:9003/
```
那么我们改造后的地址就是:
``` url
-/sso/auth?redirect=http://sa-sso-client1.com:9001/sso/login?back=http://sa-sso-client1.com:9001/
+/sso/auth?client=sso-client3&redirect=http://sa-sso-client1.com:9003/sso/login?back=http://sa-sso-client1.com:9003/
```
-格式形如:`/sso/auth?redirect=${子系统首页}/sso/login?back=${子系统首页}`
+格式形如:`/sso/auth?client={client标识}&redirect=${子系统首页}/sso/login?back=${子系统首页}`
---
### 完整代码示例:
-1、在 sso-server 中添加 `HomeController`,作为平台中心首页:
+1、在 sso-server 中配置 `home-route` 字段:
+
+
+
+``` yaml
+# Sa-Token 配置
+sa-token:
+ # SSO-Server 配置
+ sso-server:
+ # 主页路由:在 /sso/auth 登录页不指定 redirect 参数时,默认跳转的地址
+ home-route: /home
+```
+
+``` properties
+# 主页路由:在 /sso/auth 登录页不指定 redirect 参数时,默认跳转的地址
+sa-token.sso-server.home-route: /home
+```
+
+
+
+
+2、在 sso-server 中添加 `HomeController`,作为平台中心首页:
``` java
/**
@@ -39,51 +60,33 @@ http://sa-sso-client1.com:9001/
@RestController
public class HomeController {
// 平台化首页
- @RequestMapping("/home")
- public Object index() {
- // 如果未登录,则先去登录
- if(!StpUtil.isLogin()) {
- return SaHolder.getResponse().redirect("/sso/auth");
- }
-
- // 拼接各个子系统的地址,格式形如:/sso/auth?redirect=${子系统首页}/sso/login?back=${子系统首页}
- String link1 = "/sso/auth?redirect=http://sa-sso-client1.com:9001/sso/login?back=http://sa-sso-client1.com:9001/";
- String link2 = "/sso/auth?redirect=http://sa-sso-client2.com:9001/sso/login?back=http://sa-sso-client2.com:9001/";
- String link3 = "/sso/auth?redirect=http://sa-sso-client3.com:9001/sso/login?back=http://sa-sso-client3.com:9001/";
+ @RequestMapping({"/", "/home"})
+ public Object index() {
+ // 如果未登录,则先去登录
+ if(!StpUtil.isLogin()) {
+ return SaHolder.getResponse().redirect("/sso/auth");
+ }
- // 组织网页结构返回到前端
- String title = "SSO 平台首页
";
- String client1 = " 进入Client1系统
";
- String client2 = " 进入Client2系统
";
- String client3 = " 进入Client3系统
";
-
- return title + client1 + client2 + client3;
- }
+ // 拼接各个子系统的地址,格式形如:/sso/auth?client=xxx&redirect=${子系统首页}/sso/login?back=${子系统首页}
+ String link1 = "/sso/auth?client=sso-client3&redirect=http://sa-sso-client1.com:9003/sso/login?back=http://sa-sso-client1.com:9003/";
+ String link2 = "/sso/auth?client=sso-client3&redirect=http://sa-sso-client2.com:9003/sso/login?back=http://sa-sso-client2.com:9003/";
+ String link3 = "/sso/auth?client=sso-client3&redirect=http://sa-sso-client3.com:9003/sso/login?back=http://sa-sso-client3.com:9003/";
+
+ // 组织网页结构返回到前端
+ String title = "SSO 平台首页 (平台中心模式)
";
+ String client1 = " 进入Client1系统
";
+ String client2 = " 进入Client2系统
";
+ String client3 = " 进入Client3系统
";
+
+ return title + client1 + client2 + client3;
+ }
}
```
-2、修改一下SSO路由处理的代码,使登录后不再重定向到client端,而是跳转到平台中心首页。
-
-在 `SsoServerController` 的 `ssoRequest` 方法中添加跳转 `/home` 的代码:
-
-``` java
-// SSO-Server端:处理所有SSO相关请求
-@RequestMapping("/sso/*")
-public Object ssoRequest() {
- // 如果登录时没有提供redirect参数,则进入平台中心首页 /home,而不是重定向到 client 端
- SaRequest req = SaHolder.getRequest();
- if(req.isPath("/sso/auth") && req.hasParam("redirect") == false && StpUtil.isLogin()) {
- return SaHolder.getResponse().redirect("/home");
- }
- return SaSsoServerProcessor.instance.dister();
-}
-```
-
-新加代码在 4-8 行。
### 测试访问
-启动项目,访问:[http://sa-sso-server.com:9000/home](http://sa-sso-server.com:9000/home)
+启动项目,访问:[http://sa-sso-server.com:9000](http://sa-sso-server.com:9000)
首次访问,因为我们没有登录,所以会被重定向到 `/sso/auth` 登录页,我们登录上之后,便会跳转到平台中心首页:
diff --git a/sa-token-doc/sso/sso-nosdk.md b/sa-token-doc/sso/sso-nosdk.md
index db2d05d8..4d738d1f 100644
--- a/sa-token-doc/sso/sso-nosdk.md
+++ b/sa-token-doc/sso/sso-nosdk.md
@@ -2,21 +2,37 @@
---
+经常有小伙伴提问:客户端不使用 Sa-Token,能否接入 SSO 认证中心?当然是可以的。
+
+SSO-Server 所有接口都是通过 http 协议开放的,这意味着原则上只要一个语言支持 http 请求调用就可以对接 SSO-Server,参考: [SSO 认证中心开放接口](/sso/sso-apidoc)
+
### NoSdk 模式
-如果我们的 SSO 应用端不想或不能集成 Sa-Token,则可以使用 NoSdk 模式来对接。
+NoSdk 模式(不使用SDK):通过 http 工具类调用接口的方式来对接 SSO-Server。
-NoSdk 模式:通过 http 工具类调用接口的方式来对接 SSO-Server。
+参考 demo:[sa-token-demo-sso3-client-nosdk](https://gitee.com/dromara/sa-token/tree/master/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk)
-其实原理很简单,不能集成 Sa-Token 了,那我们就手动写代码模拟出 Sa-Token 在 SSO 流程所做的工作即可。
+该 demo 假设应用端没有使用任何“权限认证框架”,使用最基础的 ServletAPI 进行会话管理,模拟了 `/sso/login`、 `/sso/logout`、 `/sso/logoutCall` 三个接口的处理逻辑。
-由于所需代码较多,无法在文档处直接展示,demo 地址可参考:
-[sa-token-demo-sso3-client-nosdk](https://gitee.com/dromara/sa-token/tree/master/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-nosdk)
+> [!WARNING| label:NoSdk 示例不再主维护]
+> 基于以下原因:
+> - 1、NoSdk demo 相当于通过 http 工具类再次重写了一遍 Sa-Token SSO 模块代码,繁琐且冗余。
+> - 2、重写的代码无法拥有 Sa-Token SSO 模块全部能力,仅能完成基本对接,算是一个简化版 SDK。
+>
+> 自 v1.43.0 版本起,不再主维护 NoSdk 模式,仓库示例仅做留档参考,大家可以转为 ReSdk 模式。
-该 demo 假设应用端没有使用任何“权限认证框架”,使用最基础的 ServletAPI 进行会话管理,模拟了 `/sso/login`、`/sso/logout`、`/sso/logoutCall` 三个接口的处理逻辑。
-建议各位同学在阅读源码时结合 [SSO 认证中心开放接口](/sso/sso-apidoc) 观看。
+### ReSdk 模式
+ReSdk 模式(重写SDK部分方法):通过重写框架关键步骤点,来对接 SSO-Server。
+
+参考 demo:[sa-token-demo-sso3-client-resdk](https://gitee.com/dromara/sa-token/tree/master/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso3-client-resdk)
+
+> [!INFO| label:ReSdk 模式优点]
+> - 1、依然支持客户端使用任意技术栈。
+> - 2、仅重写少量部分关键代码,即可完成对接。几乎可以得到 Sa-Token SSO 模块全量能力。
+
+建议新项目首选 ReSdk 模式作为参考。
diff --git a/sa-token-doc/sso/sso-questions.md b/sa-token-doc/sso/sso-questions.md
index 93649329..c4af93b8 100644
--- a/sa-token-doc/sso/sso-questions.md
+++ b/sa-token-doc/sso/sso-questions.md
@@ -87,7 +87,7 @@ public class SaSsoServerApplication {
### 问:模式三配置一堆 xxx-url ,有办法简化一下吗?
可以使用 `sa-token.sso-client.server-url` 来简化:
-配置含义:配置 Server 端主机总地址,拼接在 authUrl、checkTicketUrl、getDataUrl、sloUrl 属性前面,用以简化各种 url 配置。
+配置含义:配置 Server 端主机总地址,拼接在 authUrl、getDataUrl、sloUrl 属性前面,用以简化各种 url 配置。
在开发 SSO 模块时,我们需要在 sso-client 配置认证中心的各种地址,特别是在模式三下,一般代码会变成这样:
@@ -96,8 +96,6 @@ sa-token:
sso-client:
# SSO-Server端 统一认证地址
auth-url: http://sa-sso-server.com:9000/sso/auth
- # SSO-Server端 ticket校验地址
- check-ticket-url: http://sa-sso-server.com:9000/sso/checkTicket
# 单点注销地址
slo-url: http://sa-sso-server.com:9000/sso/signout
# SSO-Server端 查询数据地址
@@ -214,21 +212,47 @@ public class SaTokenConfigure implements WebMvcConfigurer {
-### 问:sa-token.sso-server.allow-url 配置项可以做成从数据库读取的吗?
-可以,自定义 `SaSsoServerTemplate` 实现类,重写 `getAllowUrl` 方法即可:
+### 问:Client 信息可以做成从数据库读取的吗?
+可以,自定义 `SaSsoServerTemplate` 实现类,重写 `getClient` 与 `getClient` 方法即可:
``` java
/**
- * 重写 SaSsoServerTemplate 部分方法,增强功能
+ * 重写 SaSsoServerTemplate 部分方法,增强功能
*/
@Component
public class CustomSaSsoServerTemplate extends SaSsoServerTemplate {
-
- // 重写 [获取授权回调地址] 方法,改为从数据库中读取
- @Override
- public String getAllowUrl() {
- String allowUrl = ""; // 改为从数据库读取
- return allowUrl;
- }
+
+ // 获取指定 client 的配置信息
+ @Override
+ public SaSsoClientModel getClient(String client) {
+ if("sso-client1".equals(client)) {
+ SaSsoClientModel scm = new SaSsoClientModel();
+ scm.setAllowUrl("sso-client1");
+ scm.setSecretKey("kQwIOrYvnXmSDkwEiFngrKidMcdrgKor");
+ return scm;
+ }
+
+ // ...
+
+ return null;
+ }
+
+ // 返回所有 client 信息
+ @Override
+ public List getClients() {
+ // 模拟示例代码,真实项目可改为从数据查询
+
+ SaSsoClientModel scm1 = new SaSsoClientModel();
+ scm1.setAllowUrl("sso-client1");
+ scm1.setSecretKey("kQwIOrYvnXmSDkwEiFngrKidMcdrgKor");
+
+ SaSsoClientModel scm2 = new SaSsoClientModel();
+ scm2.setAllowUrl("sso-client2");
+ scm2.setSecretKey("kQwIOrYvnXmSDkwEiFngrKidMcdrgKor");
+
+ // ...
+
+ return Arrays.asList(scm1, scm2);
+ }
}
```
@@ -258,24 +282,24 @@ public class CustomSaSsoServerTemplate extends SaSsoServerTemplate {
public class SsoController {
// 处理 SSO-Server 端所有请求
- @RequestMapping({"/sso/auth", "/sso/doLogin", "/sso/checkTicket", "/sso/signout"})
+ @RequestMapping({"/sso/auth", "/sso/doLogin", "/sso/signout", "/sso/pushS"})
public Object ssoServerRequest() {
return SaSsoServerProcessor.instance.dister();
}
// 处理 SSO-Client 端所有请求
- @RequestMapping({"/sso/login", "/sso/logout", "/sso/logoutCall"})
+ @RequestMapping({"/sso/login", "/sso/logout", "/sso/logoutCall", "/sso/pushC"})
public Object ssoClientRequest() {
return SaSsoClientProcessor.instance.dister();
}
// 配置SSO相关参数
@Autowired
- private void configSsoServer(SaSsoServerConfig ssoServer) {
+ private void configSsoServer(SaSsoServerTemplate ssoServerTemplate) {
// SSO Server 配置代码,参考文档前几章 ...
}
@Autowired
- private void configSsoClient(SaSsoClientConfig ssoClient) {
+ private void configSsoClient(SaSsoClientTemplate ssoClientTemplate) {
// SSO Client 配置代码,参考文档前几章 ...
}
@@ -295,7 +319,7 @@ public class SsoController {
``` java
/**
- * Sa-Token-SSO 第二套 SSO-Server端 Controller
+ * Sa-Token-SSO 第二套 SSO-Server端 Controller
*/
@RestController
public class SsoUserServerController {
@@ -305,21 +329,23 @@ public class SsoUserServerController {
*/
public static SaSsoServerProcessor ssoUserServerProcessor = new SaSsoServerProcessor();
static {
- // 自定义一个 SaSsoTemplate 对象
+
+ // 自定义一个 getServerConfig
+ SaSsoServerConfig serverConfig = new SaSsoServerConfig();
+ serverConfig.setSecretKey("xxx");
+ // 更多配置 ...
+
+ // 自定义一个 SaSsoServerTemplate 对象
SaSsoServerTemplate ssoUserTemplate = new SaSsoServerTemplate() {
- // 使用的会话对象 是自定义的 StpUserUtil
@Override
- public StpLogic getStpLogic() {
- return StpUserUtil.stpLogic;
- }
- // 使用自定义的签名秘钥
- SaSignConfig signConfig = new SaSignConfig().setSecretKey("xxxx-新的秘钥-xxxx");
- SaSignTemplate userSignTemplate = new SaSignTemplate().setSignConfig(signConfig);
- @Override
- public SaSignTemplate getSignTemplate(String client) {
- return userSignTemplate;
+ public SaSsoServerConfig getServerConfig() {
+ return serverConfig;
}
};
+
+ // 使用自定义的 StpLogic 会话对象
+ ssoUserTemplate.setStpLogic(StpUserUtil.stpLogic);
+
// 让这个SSO请求处理器,使用的路由前缀是 /sso-user,而不是原先的 /sso
ssoUserTemplate.apiName.replacePrefix("/sso-user");
@@ -329,10 +355,9 @@ public class SsoUserServerController {
/*
* 第二套 sso-server 服务:处理所有SSO相关请求
- * http://{host}:{port}/sso-user/auth -- 单点登录授权地址,接受参数:redirect=授权重定向地址
- * http://{host}:{port}/sso-user/doLogin -- 账号密码登录接口,接受参数:name、pwd
- * http://{host}:{port}/sso-user/checkTicket -- Ticket校验接口(isHttp=true时打开),接受参数:ticket=ticket码、ssoLogoutCall=单点注销回调地址 [可选]
- * http://{host}:{port}/sso-user/signout -- 单点注销地址(isSlo=true时打开),接受参数:loginId=账号id、secretkey=接口调用秘钥
+ * http://{host}:{port}/sso-user/auth -- 单点登录授权地址
+ * http://{host}:{port}/sso-user/doLogin -- 账号密码登录接口
+ * http://{host}:{port}/sso-user/signout -- 单点注销地址(isSlo=true时打开)
*/
@RequestMapping("/sso-user/*")
public Object ssoUserRequest() {
diff --git a/sa-token-doc/sso/sso-server.md b/sa-token-doc/sso/sso-server.md
index 381942c9..99bdd3c6 100644
--- a/sa-token-doc/sso/sso-server.md
+++ b/sa-token-doc/sso/sso-server.md
@@ -2,13 +2,13 @@
在开始SSO三种模式的对接之前,我们必须先搭建一个 SSO-Server 认证中心
-> [!TIP| label:demo | style:callout]
+> [!TIP| label:demo]
> 搭建示例在官方仓库的 `/sa-token-demo/sa-token-demo-sso/sa-token-demo-sso-server/`,如遇到难点可结合源码进行测试学习,demo里有制作好的登录页面
---
### 1、添加依赖
-创建 SpringBoot 项目 `sa-token-demo-sso-server`,引入依赖:
+创建 SpringBoot 项目 `sa-token-demo-sso-server`,在引入 SpringBoot 依赖的基础上,继续引入:
@@ -27,10 +27,10 @@
${sa.top.version}
-
+
cn.dev33
- sa-token-redis-jackson
+ sa-token-redis-template
${sa.top.version}
@@ -44,11 +44,11 @@
spring-boot-starter-thymeleaf
-
+
- com.dtflys.forest
- forest-spring-boot-starter
- 1.5.26
+ cn.dev33
+ sa-token-forest
+ ${sa.top.version}
```
@@ -59,15 +59,15 @@ implementation 'cn.dev33:sa-token-spring-boot-starter:${sa.top.version}'
// Sa-Token 插件:整合SSO
implementation 'cn.dev33:sa-token-sso:${sa.top.version}'
-// Sa-Token 整合 Redis (使用 jackson 序列化方式)
-implementation 'cn.dev33:sa-token-redis-jackson:${sa.top.version}'
+// Sa-Token 整合 RedisTemplate
+implementation 'cn.dev33:sa-token-redis-template:${sa.top.version}'
implementation 'org.apache.commons:commons-pool2'
// 视图引擎(在前后端不分离模式下提供视图支持)
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
-// Http请求工具(在模式三的单点注销功能下用到,如不需要可以注释掉)
-implementation 'com.dtflys.forest:forest-spring-boot-starter:1.5.26'
+// Sa-Token 插件:整合 Forest 请求工具 (模式三需要通过 http 请求推送消息)
+implementation 'cn.dev33:sa-token-forest:1.5.26'
```
@@ -75,11 +75,11 @@ implementation 'com.dtflys.forest:forest-spring-boot-starter:1.5.26'
> [!NOTE| label:引包简化]
> 除了 `sa-token-spring-boot-starter` 和 `sa-token-sso` 以外,其它包都是可选的:
>
-> - 在 SSO 模式三时 Redis 相关包是可选的
-> - 在前后端分离模式下可以删除 thymeleaf 相关包
-> - 在不需要 SSO 模式三单点注销的情况下可以删除 http 工具包
+> - 在 SSO 模式三时 Redis 相关包是可选的。
+> - 在前后端分离模式下可以删除 thymeleaf 相关包。
+> - 在不需要 SSO 模式三单点注销的情况下可以删除 http 工具包。
>
-> 建议先完整测试三种模式之后再对pom依赖进行酌情删减。
+> 建议先完整测试三种模式之后再对 pom 依赖进行酌情删减。
### 2、开放认证接口
@@ -93,56 +93,52 @@ implementation 'com.dtflys.forest:forest-spring-boot-starter:1.5.26'
public class SsoServerController {
/**
- * SSO-Server端:处理所有SSO相关请求 (下面的章节我们会详细列出开放的接口)
+ * SSO-Server端:处理所有SSO相关请求
+ * http://{host}:{port}/sso/auth -- 单点登录授权地址
+ * http://{host}:{port}/sso/doLogin -- 账号密码登录接口,接受参数:name、pwd
+ * http://{host}:{port}/sso/signout -- 单点注销地址(isSlo=true时打开)
*/
@RequestMapping("/sso/*")
public Object ssoRequest() {
return SaSsoServerProcessor.instance.dister();
}
-
+
/**
* 配置SSO相关参数
*/
@Autowired
- private void configSso(SaSsoServerConfig ssoServer) {
+ private void configSso(SaSsoServerTemplate ssoServerTemplate) {
// 配置:未登录时返回的View
- ssoServer.notLoginView = () -> {
- String msg = "当前会话在SSO-Server端尚未登录,请先访问"
- + " doLogin登录 "
- + "进行登录之后,刷新页面开始授权";
- return msg;
+ ssoServerTemplate.strategy.notLoginView = () -> {
+ // 简化模拟表单
+ String doLoginCode =
+ "fetch(`/sso/doLogin?name=${document.querySelector('#name').value}&pwd=${document.querySelector('#pwd').value}`) " +
+ " .then(res => res.json()) " +
+ " .then(res => { if(res.code === 200) { location.reload() } else { alert(res.msg) } } )";
+ String res =
+ "当前客户端在 SSO-Server 认证中心尚未登录,请先登录
" +
+ "用户:
" +
+ "密码:
" +
+ "";
+ return res;
};
// 配置:登录处理函数
- ssoServer.doLoginHandle = (name, pwd) -> {
- // 此处仅做模拟登录,真实环境应该查询数据进行登录
+ ssoServerTemplate.strategy.doLoginHandle = (name, pwd) -> {
+ // 此处仅做模拟登录,真实环境应该查询数据库进行登录
if("sa".equals(name) && "123456".equals(pwd)) {
StpUtil.login(10001);
return SaResult.ok("登录成功!").setData(StpUtil.getTokenValue());
}
return SaResult.error("登录失败!");
};
-
- // 配置 Http 请求处理器 (在模式三的单点注销功能下用到,如不需要可以注释掉)
- ssoServer.sendHttp = url -> {
- try {
- System.out.println("------ 发起请求:" + url);
- String resStr = Forest.get(url).executeAsString();
- System.out.println("------ 请求结果:" + resStr);
- return resStr;
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }
- };
}
}
```
-注意:
-- 在`doLoginHandle`函数里如果要获取name, pwd以外的参数,可通过`SaHolder.getRequest().getParam("xxx")`来获取
-- 在 `sendHttp` 函数中,使用 `try-catch` 是为了提高整个注销流程的容错性,避免在一些极端情况下注销失败(例如:某个 Client 端上线之后又下线,导致 http 请求无法调用成功,从而阻断了整个注销流程)
+注意:在`doLoginHandle`函数里如果要获取 name, pwd 以外的参数,可通过`SaHolder.getRequest().getParam("xxx")`来获取。
+
全局异常处理:
``` java
@@ -170,25 +166,41 @@ server:
# Sa-Token 配置
sa-token:
- # ------- SSO-模式一相关配置 (非模式一不需要配置)
+ # 打印操作日志
+ is-log: true
+
+ # SSO-模式一相关配置 (非模式一不需要配置)
# cookie:
# 配置 Cookie 作用域
# domain: stp.com
- # ------- SSO-模式二相关配置
- sso-server:
+ # SSO-Server 配置
+ sso-server:
# Ticket有效期 (单位: 秒),默认五分钟
ticket-timeout: 300
- # 所有允许的授权回调地址
- allow-url: "*"
-
- # ------- SSO-模式三相关配置 (下面的配置在使用SSO模式三时打开)
- # 是否打开模式三
- is-http: true
- sign:
- # API 接口调用秘钥
- secret-key: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
- # ---- 除了以上配置项,你还需要为 Sa-Token 配置http请求处理器(文档有步骤说明)
+ # 应用列表:配置接入的应用信息
+ clients:
+ # 应用 sso-client1:采用模式一对接 (同域、同Redis)
+ sso-client1:
+ client: sso-client1
+ allow-url: "*"
+ # 应用 sso-client2:采用模式二对接 (跨域、同Redis)
+ sso-client2:
+ client: sso-client2
+ allow-url: "*"
+ secret-key: SSO-C2-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
+ # 应用 sso-client3:采用模式三对接 (跨域、跨Redis)
+ sso-client3:
+ # 应用名称
+ client: sso-client3
+ # 允许授权地址
+ allow-url: "*"
+ # 是否接收消息推送
+ is-push: true
+ # 消息推送地址
+ push-url: http://sa-sso-client1.com:9003/sso/pushC
+ # 接口调用秘钥 (如果不配置则使用全局默认秘钥)
+ secret-key: SSO-C3-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
spring:
# Redis配置 (SSO模式一和模式二使用Redis来同步会话)
@@ -201,10 +213,6 @@ spring:
port: 6379
# Redis服务器连接密码(默认为空)
password:
-
-forest:
- # 关闭 forest 请求日志打印
- log-enabled: false
```
``` properties
@@ -212,25 +220,40 @@ forest:
server.port=9000
################## Sa-Token 配置 ##################
-# ------- SSO-模式一相关配置 (非模式一不需要配置)
+# 打印操作日志
+sa-token.is-log=true
+
+# SSO-模式一相关配置 (非模式一不需要配置)
# 配置 Cookie 作用域
# sa-token.cookie.domain=stp.com
-# ------- SSO-模式二相关配置
+# SSO-Server 配置
# Ticket有效期 (单位: 秒),默认五分钟
sa-token.sso-server.ticket-timeout=300
-# 所有允许的授权回调地址
-sa-token.sso-server.allow-url=*
-# ------- SSO-模式三相关配置 (下面的配置在使用SSO模式三时打开)
-# 是否打开模式三
-sa-token.sso-server.is-http=true
-# API 接口调用秘钥
-sa-token.sign.secret-key=kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
+# 应用列表:配置接入的应用信息
+# 应用 sso-client1:采用模式一对接 (同域、同Redis)
+sa-token.sso-server.clients.sso-client1.client=sso-client1
+sa-token.sso-server.clients.sso-client1.allow-url=*
-# ---- 除了以上配置项,你还需要为 Sa-Token 配置http请求处理器(文档有步骤说明)
-
-################## Redis配置 (SSO模式一和模式二使用Redis来同步会话) ##################
+# 应用 sso-client2:采用模式二对接 (跨域、同Redis)
+sa-token.sso-server.clients.sso-client2.client=sso-client2
+sa-token.sso-server.clients.sso-client2.allow-url=*
+sa-token.sso-server.clients.sso-client2.secret-key=SSO-C2-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
+
+# 应用 sso-client3:采用模式三对接 (跨域、跨Redis)
+# 应用名称
+sa-token.sso-server.clients.sso-client3.client=sso-client3
+# 允许授权地址
+sa-token.sso-server.clients.sso-client3.allow-url=*
+# 是否接收消息推送
+sa-token.sso-server.clients.sso-client3.is-push=true
+# 消息推送地址
+sa-token.sso-server.clients.sso-client3.push-url=http://sa-sso-client1.com:9003/sso/pushC
+# 接口调用秘钥 (如果不配置则使用全局默认秘钥)
+sa-token.sso-server.clients.sso-client3.secret-key=SSO-C3-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
+
+# Redis配置 (SSO模式一和模式二使用Redis来同步会话)
# Redis数据库索引(默认为0)
spring.redis.database=1
# Redis服务器地址
@@ -239,14 +262,11 @@ spring.redis.host=127.0.0.1
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
-
-# 关闭 forest 请求日志打印
-forest.log-enabled: false
```
-注意点:`sa-token.sso-server.allow-url`为了方便测试配置为`*`,线上生产环境一定要配置为详细URL地址 (之后的章节我们会详细阐述此配置项)
+注意点:`sa-token.sso-server.clients.xxx.allow-url`为了方便测试配置为`*`,线上生产环境一定要配置为详细 URL 地址 (之后的章节我们会详细阐述此配置项)
### 4、创建启动类
@@ -259,6 +279,8 @@ public class SaSsoServerApplication {
System.out.println();
System.out.println("---------------------- Sa-Token SSO 统一认证中心启动成功 ----------------------");
System.out.println("配置信息:" + SaSsoManager.getServerConfig());
+ System.out.println("统一认证登录地址:http://sa-sso-server.com:9000/sso/auth");
+ System.out.println("测试前需要根据官网文档修改 hosts 文件,测试账号密码:sa / 123456");
System.out.println();
}
}
@@ -271,7 +293,7 @@ public class SaSsoServerApplication {
访问统一授权地址(仅测试 SSO-Server 是否部署成功,暂时还不需要点击登录):
- [http://localhost:9000/sso/auth](http://localhost:9000/sso/auth)
-
+
可以看到这个页面目前非常简陋,这是因为我们以上的代码示例,主要目标是为了带大家从零搭建一个可用的SSO认证服务端,所以就对一些不太必要的步骤做了简化。
@@ -280,7 +302,7 @@ public class SaSsoServerApplication {

默认账号密码为:`sa / 123456`,先别着急点击登录,因为我们还没有搭建对应的 Client 端项目,
-真实项目中我们是不会直接从浏览器访问 `/sso/auth` 授权地址的,我们需要在 Client 端点击登录按钮重定向而来。
+真实项目中我们一般不会直接从浏览器访问 `/sso/auth` 授权地址的,我们需要在 Client 端点击登录按钮重定向而来。
---
diff --git a/sa-token-doc/sso/sso-type1.md b/sa-token-doc/sso/sso-type1.md
index 0d858022..7ffcdaad 100644
--- a/sa-token-doc/sso/sso-type1.md
+++ b/sa-token-doc/sso/sso-type1.md
@@ -58,7 +58,7 @@ sa-token.cookie.domain=stp.com
```
-这个配置原本是被注释掉的,现在将其打开。另外我们格外需要注意:
+**这个配置原本是被注释掉的,现在将其打开。**另外我们格外需要注意:
在SSO模式一测试完毕之后,一定要将这个配置再次注释掉,因为模式一与模式二三使用不同的授权流程,这行配置会影响到我们模式二和模式三的正常运行。
@@ -88,10 +88,10 @@ sa-token.cookie.domain=stp.com
${sa.top.version}
-
+
cn.dev33
- sa-token-redis-jackson
+ sa-token-redis-template
${sa.top.version}
@@ -114,8 +114,8 @@ implementation 'cn.dev33:sa-token-spring-boot-starter:${sa.top.version}'
// Sa-Token 插件:整合SSO
implementation 'cn.dev33:sa-token-sso:${sa.top.version}'
-// Sa-Token 整合 Redis (使用 jackson 序列化方式)
-implementation 'cn.dev33:sa-token-redis-jackson:${sa.top.version}'
+// Sa-Token 整合 RedisTemplate
+implementation 'cn.dev33:sa-token-redis-template:${sa.top.version}'
implementation 'org.apache.commons:commons-pool2'
// Sa-Token插件:权限缓存与业务缓存分离
@@ -136,13 +136,16 @@ public class SsoClientController {
// SSO-Client端:首页
@RequestMapping("/")
- public String index() {
- String authUrl = SaSsoManager.getClientConfig().splicingAuthUrl();
- String solUrl = SaSsoManager.getClientConfig().splicingSloUrl();
- String str = "Sa-Token SSO-Client 应用端
" +
- "当前会话是否登录:" + StpUtil.isLogin() + "
" +
- "登录 " +
- "注销
";
+ public String index(HttpServletRequest request) {
+ String url = SaFoxUtil.encodeUrl( SaFoxUtil.joinParam(SaHolder.getRequest().getUrl(), request.getQueryString()) );
+ SaSsoClientConfig cfg = SaSsoManager.getClientConfig();
+
+ String str = "Sa-Token SSO-Client 应用端 (模式一)
" +
+ "当前会话是否登录:" + StpUtil.isLogin() + " (" + StpUtil.getLoginId("") + ")
" +
+ "" +
+ "登录 - " +
+ "注销 " +
+ "
";
return str;
}
@@ -169,10 +172,13 @@ server:
sa-token:
# SSO-相关配置
sso-client:
+ # client 标识
+ client: sso-client1
# SSO-Server端主机地址
server-url: http://sso.stp.com:9000
-
- # 配置 Sa-Token 单独使用的Redis连接 (此处需要和SSO-Server端连接同一个Redis)
+
+ # 配置 Sa-Token 单独使用的Redis连接(此处需要和 SSO-Server 端连接同一个 Redis)
+ # 注:使用 alone-redis 需要在 pom.xml 引入 sa-token-alone-redis 依赖
alone-redis:
# Redis数据库索引
database: 1
@@ -192,10 +198,13 @@ server.port=9001
######### Sa-Token 配置 #########
+# client 标识
+sa-token.sso-client.client=sso-client1
# SSO-Server端主机地址
sa-token.sso-client.server-url=http://sso.stp.com:9000
-
-# 配置 Sa-Token 单独使用的Redis连接 (此处需要和SSO-Server端连接同一个Redis)
+
+# 配置 Sa-Token 单独使用的Redis连接(此处需要和 SSO-Server 端连接同一个 Redis)
+# 注:使用 alone-redis 需要在 pom.xml 引入 sa-token-alone-redis 依赖
# Redis数据库索引
sa-token.alone-redis.database=1
# Redis服务器地址
@@ -247,9 +256,9 @@ public class SaSso1ClientApplication {
然后点击登录,被重定向至SSO认证中心:
-
+
-我们点击登录,然后刷新页面:
+我们登录之后,然后刷新页面:

diff --git a/sa-token-doc/sso/sso-type2.md b/sa-token-doc/sso/sso-type2.md
index a996c070..751bf84a 100644
--- a/sa-token-doc/sso/sso-type2.md
+++ b/sa-token-doc/sso/sso-type2.md
@@ -89,10 +89,10 @@ sa-token.cookie.domain=stp.com
${sa.top.version}
-
+
cn.dev33
- sa-token-redis-jackson
+ sa-token-redis-template
${sa.top.version}
@@ -106,6 +106,13 @@ sa-token.cookie.domain=stp.com
sa-token-alone-redis
${sa.top.version}
+
+
+
+ cn.dev33
+ sa-token-forest
+ ${sa-token.version}
+
```
``` gradle
@@ -115,12 +122,15 @@ implementation 'cn.dev33:sa-token-spring-boot-starter:${sa.top.version}'
// Sa-Token 插件:整合SSO
implementation 'cn.dev33:sa-token-sso:${sa.top.version}'
-// Sa-Token 整合 Redis (使用 jackson 序列化方式)
-implementation 'cn.dev33:sa-token-redis-jackson:${sa.top.version}'
+// Sa-Token 整合 RedisTemplate
+implementation 'cn.dev33:sa-token-redis-template:${sa.top.version}'
implementation 'org.apache.commons:commons-pool2'
// Sa-Token插件:权限缓存与业务缓存分离
implementation 'cn.dev33:sa-token-alone-redis:${sa.top.version}'
+
+// Sa-Token插件:整合 Forest 请求工具
+implementation 'cn.dev33:sa-token-forest:${sa.top.version}'
```
@@ -140,28 +150,58 @@ public class SsoClientController {
// 首页
@RequestMapping("/")
public String index() {
- String solUrl = SaSsoManager.getClientConfig().splicingSloUrl();
- String str = "Sa-Token SSO-Client 应用端
" +
- "当前会话是否登录:" + StpUtil.isLogin() + "
" +
- "登录 " +
- "注销
";
+ String str = "Sa-Token SSO-Client 应用端 (模式二)
" +
+ "当前会话是否登录:" + StpUtil.isLogin() + " (" + StpUtil.getLoginId("") + ")
" +
+ " " +
+ "登录 - " +
+ "单应用注销 - " +
+ "全端注销 - " +
+ "账号资料" +
+ "
";
return str;
}
/*
* SSO-Client端:处理所有SSO相关请求
- * http://{host}:{port}/sso/login -- Client端登录地址,接受参数:back=登录后的跳转地址
- * http://{host}:{port}/sso/logout -- Client端单点注销地址(isSlo=true时打开),接受参数:back=注销后的跳转地址
- * http://{host}:{port}/sso/logoutCall -- Client端单点注销回调地址(isSlo=true时打开),此接口为框架回调,开发者无需关心
+ * http://{host}:{port}/sso/login -- Client 端登录地址
+ * http://{host}:{port}/sso/logout -- Client 端注销地址(isSlo=true时打开)
+ * http://{host}:{port}/sso/pushC -- Client 端接收消息推送地址
*/
@RequestMapping("/sso/*")
public Object ssoRequest() {
return SaSsoClientProcessor.instance.dister();
}
+ // 配置SSO相关参数
+ @Autowired
+ private void configSso(SaSsoClientTemplate ssoClientTemplate) {
+
+ }
+
+ // 当前应用独自注销 (不退出其它应用)
+ @RequestMapping("/sso/logoutByAlone")
+ public Object logoutByAlone() {
+ StpUtil.logout();
+ return SaSsoClientProcessor.instance._ssoLogoutBack(SaHolder.getRequest(), SaHolder.getResponse());
+ }
+
}
```
+全局异常处理:
+``` java
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+ // 全局异常拦截
+ @ExceptionHandler
+ public SaResult handlerException(Exception e) {
+ e.printStackTrace();
+ return SaResult.error(e.getMessage());
+ }
+}
+```
+
+
##### 3.4、配置SSO认证中心地址
你需要在 `application.yml` 配置如下信息:
@@ -170,16 +210,23 @@ public class SsoClientController {
``` yaml
# 端口
server:
- port: 9001
+ port: 9002
# sa-token配置
sa-token:
+ # 打印操作日志
+ is-log: true
# SSO-相关配置
sso-client:
+ # 应用标识
+ client: sso-client2
# SSO-Server 端主机地址
server-url: http://sa-sso-server.com:9000
+ # API 接口调用秘钥 (单点注销时会用到)
+ secret-key: SSO-C2-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
- # 配置Sa-Token单独使用的Redis连接 (此处需要和SSO-Server端连接同一个Redis)
+ # 配置 Sa-Token 单独使用的Redis连接(此处需要和 SSO-Server 端连接同一个 Redis)
+ # 注:使用 alone-redis 需要在 pom.xml 引入 sa-token-alone-redis 依赖
alone-redis:
# Redis数据库索引 (默认为0)
database: 1
@@ -195,13 +242,20 @@ sa-token:
``` properties
# 端口
-server.port=9001
+server.port=9002
######### Sa-Token 配置 #########
+# 打印操作日志
+sa-token.is-log=true
+# 应用标识
+sa-token.sso-client.client=sso-client2
# SSO-Server端 统一认证地址
sa-token.sso-client.server-url=http://sa-sso-server.com:9000
+# API 接口调用秘钥 (单点注销时会用到)
+sa-token.sso-client.secret-key=SSO-C2-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
-# 配置 Sa-Token 单独使用的Redis连接 (此处需要和SSO-Server端连接同一个Redis)
+# 配置 Sa-Token 单独使用的Redis连接(此处需要和 SSO-Server 端连接同一个 Redis)
+# 注:使用 alone-redis 需要在 pom.xml 引入 sa-token-alone-redis 依赖
# Redis数据库索引
sa-token.alone-redis.database=1
# Redis服务器地址
@@ -228,9 +282,9 @@ public class SaSso2ClientApplication {
System.out.println();
System.out.println("---------------------- Sa-Token SSO 模式二 Client 端启动成功 ----------------------");
System.out.println("配置信息:" + SaSsoManager.getClientConfig());
- System.out.println("测试访问应用端一: http://sa-sso-client1.com:9001");
- System.out.println("测试访问应用端二: http://sa-sso-client2.com:9001");
- System.out.println("测试访问应用端三: http://sa-sso-client3.com:9001");
+ System.out.println("测试访问应用端一: http://sa-sso-client1.com:9002");
+ System.out.println("测试访问应用端二: http://sa-sso-client2.com:9002");
+ System.out.println("测试访问应用端三: http://sa-sso-client3.com:9002");
System.out.println("测试前需要根据官网文档修改hosts文件,测试账号密码:sa / 123456");
System.out.println();
}
@@ -241,27 +295,27 @@ public class SaSso2ClientApplication {
### 4、测试访问
-(1) 依次启动 `SSO-Server` 与 `SSO-Client`,然后从浏览器访问:[http://sa-sso-client1.com:9001/](http://sa-sso-client1.com:9001/)
+(1) 依次启动 `SSO-Server` 与 `SSO-Client`,然后从浏览器访问:[http://sa-sso-client1.com:9002/](http://sa-sso-client1.com:9002/)
-
+(注:先前版本文档测试demo端口号为9001,后为了方便区分三种模式改为了9002,因此出现文字描述与截图端口号不一致情况,请注意甄别,后不再赘述)

(2) 首次打开,提示当前未登录,我们点击 **`登录`** 按钮,页面会被重定向到登录中心
-
+
-(3) SSO-Server提示我们在认证中心尚未登录,我们点击 **`doLogin登录`** 按钮进行模拟登录
+(3) SSO-Server提示我们在认证中心尚未登录,我们点击 **`登录`** 按钮进行模拟登录
-
+
-(4) SSO-Server认证中心登录成功,我们回到刚才的页面刷新页面
+(4) SSO-Server认证中心登录成功,系统重定向回 client

(5) 页面被重定向至`Client`端首页,并提示登录成功,至此,`Client1`应用已单点登录成功!
-(6) 我们再次访问`Client2`:[http://sa-sso-client2.com:9001/](http://sa-sso-client2.com:9001/)
+(6) 我们再次访问`Client2`:[http://sa-sso-client2.com:9002/](http://sa-sso-client2.com:9002/)

@@ -269,7 +323,7 @@ public class SaSso2ClientApplication {

-(8) 同样的方式,我们打开`Client3`,也可以直接登录成功:[http://sa-sso-client3.com:9001/](http://sa-sso-client3.com:9001/)
+(8) 同样的方式,我们打开`Client3`,也可以直接登录成功:[http://sa-sso-client3.com:9002/](http://sa-sso-client3.com:9002/)

@@ -291,7 +345,7 @@ public class SaSso2ClientApplication {
> - `/sa-token-demo/sa-token-demo-sso2-client/`
>
> 然后访问:
-> - [http://sa-sso-client1.com:9001/](http://sa-sso-client1.com:9001/)
+> - [http://sa-sso-client1.com:9002/](http://sa-sso-client1.com:9002/)

diff --git a/sa-token-doc/sso/sso-type3.md b/sa-token-doc/sso/sso-type3.md
index fb00d8ce..fa752397 100644
--- a/sa-token-doc/sso/sso-type3.md
+++ b/sa-token-doc/sso/sso-type3.md
@@ -22,64 +22,106 @@
### 2、在Client 端更改 Ticket 校验方式
+如果想要更直观的感受模式二与模式三的差距,可以把前面章节创建的模式二 demo 代码复制一份,在新复制的项目上继续更改来测试模式三。
-#### 2.1、增加 pom.xml 配置
+#### 2.1、去除 Alone-Redis 依赖
+
+模式三要求 sso-client 与 sso-server 连接不同的 redis,所以此处没有必要再引入 sa-token-alone-redis 机制,可以去除相关依赖:
-``` xml
-
+``` xml
+
- com.dtflys.forest
- forest-spring-boot-starter
- 1.5.26
+ cn.dev33
+ sa-token-alone-redis
+ ${sa.top.version}
```
``` gradle
-// Http请求工具
-implementation 'com.dtflys.forest:forest-spring-boot-starter:1.5.26'
+// Sa-Token插件:权限缓存与业务缓存分离
+implementation 'cn.dev33:sa-token-alone-redis:${sa.top.version}'
```
-Forest 是一个轻量级 http 请求工具,详情参考:[Forest](https://forest.dtflyx.com/)
+#### 2.2、SSO-Client 端更改配置
-#### 2.2、SSO-Client 端新增配置:API调用秘钥
-
-在 `application.yml` 增加:
+更改 `application.yml` :
``` yaml
-sa-token:
- sso-client:
- # 打开模式三(使用Http请求校验ticket)
+# 端口
+server:
+ port: 9003
+
+# sa-token配置
+sa-token:
+ # 打印操作日志
+ is-log: true
+
+ # sso-client 相关配置
+ sso-client:
+ # 应用标识
+ client: sso-client3
+ # sso-server 端主机地址
+ server-url: http://sa-sso-server.com:9000
+ # 使用 Http 请求校验 ticket (模式三)
is-http: true
- sign:
# API 接口调用秘钥
- secret-key: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
+ secret-key: SSO-C3-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
-forest:
- # 关闭 forest 请求日志打印
- log-enabled: false
+spring:
+ # 配置 Redis 连接 (此处与 SSO-Server 端连接不同的 Redis)
+ redis:
+ # Redis数据库索引
+ database: 3
+ # Redis服务器地址
+ host: 127.0.0.1
+ # Redis服务器连接端口
+ port: 6379
+ # Redis服务器连接密码(默认为空)
+ password:
+ # 连接超时时间
+ timeout: 10s
```
``` properties
-# 打开模式三(使用Http请求校验ticket)
-sa-token.sso-client.is-http=true
-# 接口调用秘钥
-sa-token.sign.secret-key=kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
+# 端口
+server.port=9003
+
+# sa-token配置
-# 关闭 forest 请求日志打印
-forest.log-enabled=false
+# 打印操作日志
+sa-token.is-log=true
+
+# sso-client 相关配置
+# 应用标识
+sa-token.sso-client.client=sso-client3
+# sso-server 端主机地址
+sa-token.sso-client.server-url=http://sa-sso-server.com:9000
+# 使用 Http 请求校验 ticket (模式三)
+sa-token.sso-client.is-http=true
+# API 接口调用秘钥
+sa-token.sso-client.secret-key=SSO-C3-kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
+
+# 配置 Redis 连接 (此处与 SSO-Server 端连接不同的 Redis)
+# Redis数据库索引
+spring.redis.database=3
+# Redis服务器地址
+spring.redis.host=127.0.0.1
+# Redis服务器连接端口
+spring.redis.port=6379
+# Redis服务器连接密码(默认为空)
+spring.redis.password=
+# 连接超时时间
+spring.redis.timeout=10s
```
-因为我们已经在控制台手动打印 url 请求日志了,所以此处 `forest.log-enabled=false` 关闭 Forest 框架自身的日志打印,这不是必须的,你可以将其打开。
-
-注意 secretkey 秘钥需要与SSO认证中心的一致
-
+
-#### 2.4、测试
+#### 2.3、测试
重启项目,访问测试:
-- [http://sa-sso-client1.com:9001/](http://sa-sso-client1.com:9001/)
-- [http://sa-sso-client2.com:9001/](http://sa-sso-client2.com:9001/)
-- [http://sa-sso-client3.com:9001/](http://sa-sso-client3.com:9001/)
+- [http://sa-sso-client1.com:9003/](http://sa-sso-client1.com:9003/)
+- [http://sa-sso-client2.com:9003/](http://sa-sso-client2.com:9003/)
+- [http://sa-sso-client3.com:9003/](http://sa-sso-client3.com:9003/)
> [!WARNING| label:小提示]
> 注:如果已测试运行模式二,可先将Redis中的数据清空,以防旧数据对测试造成干扰
+测试步骤同模式二,不再赘述。
-
-
+
-### 5、无刷单点注销
-
-有了单点登录,就必然伴随着单点注销(一处注销,全端下线)
-
-
-此处简单介绍一下 SSO 模式三的单点注销链路过程:
-
-1. sso-client 端在校验 ticket 时(调用 `http://{sso-server}/sso/checkTicket` 时),将注销回调地址 `http://{sso-client}/sso/logoutCall` 发送到 Server 端。
-2. sso-server 端将此 sso-client 的注销回调地址以 List 集合的形式存储在该账号 Access-Session 的 dataMap。
-3. sso-client 的前端向 sso-client 的后端发起单点注销请求。(调用 `http://{sso-client}/sso/logout`)
-4. sso-client 的后端向 sso-server 的后端发送单点注销请求。(调用 `http://{sso-server}/sso/signout`)
-5. sso-server 端遍历该账号 Access-Session 存储的注销回调地址集合,逐个通知 sso-client 端下线。(`http://{sso-client}/sso/logoutCall`)
-6. sso-server 端注销下线。
-7. sso-server 后端响应 sso-client 后端:注销完成。
-7. sso-client 后端响应 sso-client 前端:注销完成。
-8. 整体完成。
-
-
-
-
-
-这些逻辑 Sa-Token 内部已经封装完毕,你只需按照文档步骤集成即可。
-
-#### 5.1、更改注销方案
-
-将 sso-client 首页路由方法里的注销链接换成 `/sso/logout` 接口:
-``` java
-// SSO-Client端:首页
-@RequestMapping("/")
-public String index() {
- String str = "Sa-Token SSO-Client 应用端
" +
- "当前会话是否登录:" + StpUtil.isLogin() + "
" +
- "登录" +
- " 注销
";
- return str;
-}
-```
-
-#### 5.2、启动测试
-重启项目,依次登录三个 client:
-- [http://sa-sso-client1.com:9001/](http://sa-sso-client1.com:9001/)
-- [http://sa-sso-client2.com:9001/](http://sa-sso-client2.com:9001/)
-- [http://sa-sso-client3.com:9001/](http://sa-sso-client3.com:9001/)
-
-
-
-在任意一个 client 里,点击 **`[注销]`** 按钮,即可单点注销成功(打开另外两个client,刷新一下页面,登录态丢失)。
-
-
-
-
-
-PS:这里我们为了方便演示,使用的是超链接跳页面的形式,正式项目中使用 Ajax 调用接口即可做到无刷单点登录退出。
-
-例如,我们使用 [Apifox 接口测试工具](https://www.apifox.cn/) 可以做到同样的效果:
-
-
-
-测试完毕!
-
-
-
-### 6、后记
+### 3、后记
当我们熟读三种模式的单点登录之后,其实不难发现:所谓单点登录,其本质就是多个系统之间的会话共享。
当我们理解这一点之后,三种模式的工作原理也浮出水面:
diff --git a/sa-token-doc/sso/user-data-sync.md b/sa-token-doc/sso/user-data-sync.md
index 84e39e8c..62584994 100644
--- a/sa-token-doc/sso/user-data-sync.md
+++ b/sa-token-doc/sso/user-data-sync.md
@@ -102,12 +102,12 @@
``` java
// 配置SSO相关参数
@Autowired
-private void configSso(SaSsoServerConfig ssoServer) {
+private void configSso(SaSsoServerTemplate ssoServerTemplate) {
// 其它配置 ...
// 配置:Ticket校验函数
- ssoServer.checkTicketAppendData = (loginId, result) -> {
+ ssoServerTemplate.strategy.checkTicketAppendData = (loginId, result) -> {
System.out.println("-------- 追加返回信息到 sso-client --------");
// 在校验 ticket 后,给 sso-client 端追加返回信息的函数
@@ -126,12 +126,12 @@ private void configSso(SaSsoServerConfig ssoServer) {
``` java
// 配置SSO相关参数
@Autowired
-private void configSso(SaSsoClientConfig ssoClient) {
+private void configSso(SaSsoClientTemplate ssoClientTemplate) {
// 其它配置 ...
// 自定义校验 ticket 返回值的处理逻辑 (每次从认证中心获取校验 ticket 的结果后调用)
- ssoClient.ticketResultHandle = (ctr, back) -> {
+ ssoClientTemplate.strategy.ticketResultHandle = (ctr, back) -> {
System.out.println("--------- 自定义 ticket 校验结果处理函数 ---------");
System.out.println("此账号在 sso-server 的 userId:" + ctr.loginId);
System.out.println("此账号在 sso-server 会话剩余有效期:" + ctr.remainSessionTimeout + " 秒");
@@ -192,12 +192,12 @@ private void configSso(SaSsoClientConfig ssoClient) {
``` java
// 配置SSO相关参数
@Autowired
-private void configSso(SaSsoClientConfig ssoClient) {
+private void configSso(SaSsoClientTemplate ssoClientTemplate) {
// 其它配置 ...
// 自定义校验 ticket 返回值的处理逻辑 (每次从认证中心获取校验 ticket 的结果后调用)
- ssoClient.ticketResultHandle = (ctr, back) -> {
+ ssoClientTemplate.strategy.ticketResultHandle = (ctr, back) -> {
System.out.println("--------- 自定义 ticket 校验结果处理函数 ---------");
System.out.println("此账号在 sso-server 的 userId:" + ctr.loginId);
System.out.println("此账号在 sso-server 会话剩余有效期:" + ctr.remainSessionTimeout + " 秒");
@@ -240,3 +240,66 @@ private void configSso(SaSsoClientConfig ssoClient) {
> - 5.1 查的到,证明有账号,直接登录。
> - 5.2 查不到,证明无账号,程序自动给他添加一条 user 账号,并登录。
> 6. 登录完成。
+
+
+
+### 5、解决模式三下,loginId 与 centerId 不一致的问题
+
+按照字段关联法登录之后,如果一个用户在本地应用端的 userId 和认证中心端的 userId 不一致,则可能发生单点注销失败的情况:
+
+假设,一个用户在认证中心的 userId=10002,在本地应用端的 userId=100335,
+则在本地应用端发起单点注销时,其传递的 loginId 值是 100335,在 sso-server 是找不到 userId=100335 用户的,自然无法单点注销成功。
+
+解决方案是在本地应用端重写 loginId 与 centerId 转换策略函数,做到本地应用 userId 与认证中心 userId 的互相映射:
+
+``` java
+@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 不一致时的转换写法(演示的逻辑为添加和裁剪指定前缀),真实项目中,应该根据用户表存储的映射关系来做查询返回。
+
+值得注意的是,在重写转换策略后,我们在消息推送时也应该严格按照转换写法提交 loginId 参数,例如:
+
+``` java
+// 查询我的账号信息: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;
+}
+```
\ No newline at end of file
diff --git a/sa-token-doc/static/doc.css b/sa-token-doc/static/doc.css
index 497d5cd4..cc648791 100644
--- a/sa-token-doc/static/doc.css
+++ b/sa-token-doc/static/doc.css
@@ -207,6 +207,8 @@ body{
.lang-html .token.tag .attr-name *{color: #A6E22E; opacity: 0.9;}
.lang-html .token.tag .attr-value,
.lang-html .token.tag .attr-value *{color: #E6DB74; opacity: 0.9;}
+.lang-html .token.annotation.punctuation{color: #ddd;}
+.lang-html .token.punctuation{color: #ddd;}
/* java语言样式优化 */
.main-box .lang-java{color: #01a252 !important;; opacity: 1;}