mirror of
https://gitee.com/dromara/sa-token.git
synced 2025-10-07 15:14:23 +08:00
docs: 完善 SSO 章节文档
This commit is contained in:
@@ -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)
|
||||
<!-- - [不同 Client 不同秘钥](/sso/sso-diff-key) -->
|
||||
- [用户数据同步 / 迁移](/sso/user-data-sync)
|
||||
- [NoSdk、ReSdk 模式与非 java 项目](/sso/sso-nosdk)
|
||||
- [SSO 代码 API 参考](/sso/sso-dev)
|
||||
- [常见问题总结](/sso/sso-questions)
|
||||
- [Sa-Sso-Pro:单点登录商业版](/sso/sso-pro)
|
||||
|
||||
|
@@ -2,4 +2,4 @@
|
||||
|
||||
<!-- 参考:[https://blog.csdn.net/shengzhang_/article/details/119928794](https://blog.csdn.net/shengzhang_/article/details/119928794) -->
|
||||
|
||||
参考:[https://juejin.cn/post/7247376558367981627](https://juejin.cn/post/7247376558367981627)
|
||||
参考:[https://juejin.cn/post/7491603065944129590](https://juejin.cn/post/7491603065944129590)
|
||||
|
108
sa-token-doc/sso/anon-client.md
Normal file
108
sa-token-doc/sso/anon-client.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# 匿名 Client 接入
|
||||
|
||||
匿名 Client 就是指在客户端没有配置 `sso-client` 的应用,没有一个明确的 “Client” 标识名称。
|
||||
|
||||
匿名 Client 在一些关键步骤中不会构建 `client` 参数,如:“重定向至认证中心授权地址”、“校验 ticket”、“单点注销” 等。
|
||||
|
||||
要想匿名 client 接入,你需要做一些特殊配置。
|
||||
|
||||
|
||||
### 1、在 sso-server 端开启匿名 client 接入
|
||||
|
||||
开启方式一,通过配置项方式:
|
||||
|
||||
<!---------------------------- tabs:start ---------------------------->
|
||||
<!------------- tab:yaml 风格 ------------->
|
||||
``` yaml
|
||||
# Sa-Token 配置
|
||||
sa-token:
|
||||
# SSO-Server 配置
|
||||
sso-server:
|
||||
# 是否启用匿名 client (开启匿名 client 后,允许客户端接入时不提交 client 参数)
|
||||
allow-anon-client: true
|
||||
# 所有允许的授权回调地址 (匿名 client 使用)
|
||||
allow-url: "*"
|
||||
# API 接口调用秘钥 (全局默认 + 匿名 client 使用)
|
||||
secret-key: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
|
||||
```
|
||||
<!------------- tab:properties 风格 ------------->
|
||||
``` 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
|
||||
```
|
||||
<!---------------------------- tabs:end ---------------------------->
|
||||
|
||||
|
||||
开启方式二,通过代码重写方式:
|
||||
|
||||
``` 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 端有了备案,也就可以成功通知此应用发起单点注销掉了。
|
||||
|
||||
如果当前应用不需要单点注销可以不配置此字段。
|
||||
|
107
sa-token-doc/sso/message-push.md
Normal file
107
sa-token-doc/sso/message-push.md
Normal file
@@ -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、应用端调用消息推送接口获取数据
|
||||
|
||||
首先保证在配置文件里要配置上消息推送的具体地址
|
||||
|
||||
<!---------------------------- tabs:start ---------------------------->
|
||||
<!------------- tab:yaml 风格 ------------->
|
||||
``` 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
|
||||
```
|
||||
<!------------- tab:properties 风格 ------------->
|
||||
``` 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
|
||||
```
|
||||
<!---------------------------- tabs:end ---------------------------->
|
||||
|
||||
|
||||
然后在需要拉取资料的地方:
|
||||
|
||||
``` 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;
|
||||
}
|
||||
```
|
||||
|
158
sa-token-doc/sso/signout.md
Normal file
158
sa-token-doc/sso/signout.md
Normal file
@@ -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. 整体完成。
|
||||
|
||||
|
||||
<button class="show-img" img-src="https://oss.dev33.cn/sa-token/doc/g/g3--sso3-logout.gif">加载动态演示图</button>
|
||||
|
||||
|
||||
这些逻辑 Sa-Token 内部已经封装完毕,你只需按照文档步骤集成即可。以模式三 demo 为例:
|
||||
|
||||
#### 2.1、更改注销方案
|
||||
|
||||
单点注销是 Sa-Token SSO 内部已封装的接口,无需手动再添加,只需要在前端调用即可。
|
||||
|
||||
``` java
|
||||
// SSO-Client端:首页
|
||||
@RequestMapping("/")
|
||||
public String index() {
|
||||
String str = "<h2>Sa-Token SSO-Client 应用端 (模式三)</h2>" +
|
||||
"<p>当前会话是否登录:" + StpUtil.isLogin() + " (" + StpUtil.getLoginId("") + ")</p>" +
|
||||
"<p> " +
|
||||
"<a href='/sso/login?back=/'>登录</a> - " +
|
||||
"<a href='/sso/logoutByAlone?back=/'>单应用注销</a> - " +
|
||||
"<a href='/sso/logout?back=self'>全端注销</a> " +
|
||||
"</p>";
|
||||
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 = "<h2>Sa-Token SSO-Client 应用端 (模式三)</h2>" +
|
||||
"<p>当前会话是否登录:" + StpUtil.isLogin() + " (" + StpUtil.getLoginId("") + ")</p>" +
|
||||
"<p> " +
|
||||
"<a href='/sso/login?back=/'>登录</a> - " +
|
||||
"<a href='/sso/logoutByAlone?back=/'>单应用注销</a> - " +
|
||||
"<a href='/sso/logout?back=self&singleDeviceIdLogout=true'>单浏览器注销</a> - " +
|
||||
"<a href='/sso/logout?back=self'>全端注销</a> " +
|
||||
"</p>";
|
||||
return str;
|
||||
}
|
||||
```
|
||||
|
||||
重点在第 9 行。
|
||||
|
||||
|
||||
> [!WARNING| label:测试注意点]
|
||||
> 在进行测试时,同时将一个浏览器双击打开两次,是不算 “不同浏览器” 的,虽然你打开了两个浏览器窗口,但是这两个浏览器的会话数据是互通的。
|
||||
>
|
||||
> 必须打开两个不同的浏览器来测试,或者按快捷键 `ctrl + shift + N` 打开隐私模式,才可以做到会话相互隔离。
|
||||
|
||||
|
@@ -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
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@@ -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 将无法单点登录成功。
|
||||
|
||||
为了方便测试,上述代码将其配置为`*`,但是,<font color="#FF0000" >在生产环境中,此配置项绝对不能配置为 * </font>,否则会有被 Ticket 劫持的风险
|
||||
为了方便测试,上述代码将其配置为`*`,但是,<font color="#FF0000" >在生产环境中,此配置项绝对不能配置为 * </font>,否则会有被 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
|
||||
```
|
||||
<!------------- tab:properties 风格 ------------->
|
||||
``` 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
|
||||
```
|
||||
<!---------------------------- tabs:end ---------------------------->
|
||||
|
||||
|
@@ -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();
|
||||
}
|
||||
|
||||
// ... 其它方法
|
||||
|
||||
}
|
||||
```
|
@@ -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 标识:
|
||||
|
||||
|
||||
<!---------------------------- tabs:start ---------------------------->
|
||||
@@ -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)) {
|
||||
|
143
sa-token-doc/sso/sso-dev.md
Normal file
143
sa-token-doc/sso/sso-dev.md
Normal file
@@ -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<T> 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)
|
||||
|
||||
|
@@ -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
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Sa-Token-SSO-Client端-测试页(前后端分离版)</title>
|
||||
<title>Sa-Token-SSO-Client端-测试页(前后端分离版-原生h5)</title>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Sa-Token SSO-Client 应用端(前后端分离版)</h2>
|
||||
<h2>Sa-Token SSO-Client 应用端(前后端分离版-原生h5)</h2>
|
||||
<p>当前是否登录:<b class="is-login"></b></p>
|
||||
<p>
|
||||
<a href="javascript:location.href='sso-login.html?back=' + encodeURIComponent(location.href);">登录</a>
|
||||
<a href="javascript:location.href=baseUrl + '/sso/logout?satoken=' + localStorage.satoken + '&back=' + encodeURIComponent(location.href);">注销</a>
|
||||
<a href="javascript: login();">登录</a> -
|
||||
<a href="javascript: doLogoutByAlone();">单应用注销</a> -
|
||||
<a href="javascript: doLogoutBySingleDeviceId();">单浏览器注销</a> -
|
||||
<a href="javascript: doLogout();">全端注销</a> -
|
||||
<a href="javascript: doMyInfo();">账号资料</a>
|
||||
</p>
|
||||
<script src="https://unpkg.zhimg.com/jquery@3.4.1/dist/jquery.min.js"></script>
|
||||
<script src="sso-common.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
// 后端接口地址
|
||||
var baseUrl = "http://sa-sso-client1.com:9001";
|
||||
|
||||
// 查询当前会话是否登录
|
||||
$.ajax({
|
||||
url: baseUrl + '/sso/isLogin',
|
||||
type: "post",
|
||||
dataType: 'json',
|
||||
headers: {
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"satoken": localStorage.getItem("satoken")
|
||||
},
|
||||
success: function(res){
|
||||
$('.is-login').html(res.data + '');
|
||||
},
|
||||
error: function(xhr, type, errorThrown){
|
||||
return alert("异常:" + JSON.stringify(xhr));
|
||||
}
|
||||
});
|
||||
|
||||
// 登录
|
||||
function login() {
|
||||
location.href = 'sso-login.html?back=' + encodeURIComponent(location.href);
|
||||
}
|
||||
|
||||
// 单应用注销
|
||||
function doLogoutByAlone() {
|
||||
ajax('/sso/logoutByAlone', {}, function(res){
|
||||
doIsLogin();
|
||||
})
|
||||
}
|
||||
|
||||
// 单浏览器注销
|
||||
function doLogoutBySingleDeviceId() {
|
||||
ajax('/sso/logout', { singleDeviceIdLogout: true }, function(res){
|
||||
doIsLogin();
|
||||
})
|
||||
}
|
||||
|
||||
// 全端注销
|
||||
function doLogout() {
|
||||
ajax('/sso/logout', { }, function(res){
|
||||
doIsLogin();
|
||||
})
|
||||
}
|
||||
|
||||
// 账号资料
|
||||
function doMyInfo() {
|
||||
ajax('/sso/myInfo', { }, function(res){
|
||||
alert(JSON.stringify(res));
|
||||
})
|
||||
}
|
||||
|
||||
// 判断是否登录
|
||||
function doIsLogin() {
|
||||
ajax('/sso/isLogin', {}, function(res){
|
||||
if(res.data) {
|
||||
setHtml('.is-login', res.data + ' (' + res.loginId + ')');
|
||||
} else {
|
||||
setHtml('.is-login', res.data);
|
||||
}
|
||||
})
|
||||
}
|
||||
doIsLogin();
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### 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
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Sa-Token-SSO-Client端-登录中转页页</title>
|
||||
<style type="text/css">
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-box">
|
||||
加载中 ...
|
||||
</div>
|
||||
<script src="sso-common.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
var back = getParam('back', '/');
|
||||
var ticket = getParam('ticket');
|
||||
|
||||
window.onload = function(){
|
||||
if(ticket) {
|
||||
doLoginByTicket(ticket);
|
||||
} else {
|
||||
goSsoAuthUrl();
|
||||
}
|
||||
}
|
||||
|
||||
// 重定向至认证中心
|
||||
function goSsoAuthUrl() {
|
||||
ajax('/sso/getSsoAuthUrl', {clientLoginUrl: location.href}, function(res) {
|
||||
location.href = res.data;
|
||||
})
|
||||
}
|
||||
|
||||
// 根据ticket值登录
|
||||
function doLoginByTicket(ticket) {
|
||||
ajax('/sso/doLoginByTicket', {ticket: ticket}, function(res) {
|
||||
localStorage.setItem('satoken', res.data);
|
||||
location.href = decodeURIComponent(back);
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
|
||||
### 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` ,例如:
|
||||
|
||||
<!---------------------------- tabs:start ---------------------------->
|
||||
<!------------- tab:yaml 风格 ------------->
|
||||
``` 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
|
||||
```
|
||||
<!------------- tab:properties 风格 ------------->
|
||||
``` 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
|
||||
```
|
||||
<!---------------------------- tabs:end ---------------------------->
|
||||
|
||||
|
||||
然后我们启动项目 `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 端的授权登录。
|
||||
|
||||
|
@@ -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` 字段:
|
||||
|
||||
<!---------------------------- tabs:start ---------------------------->
|
||||
<!------------- tab:yaml 风格 ------------->
|
||||
``` yaml
|
||||
# Sa-Token 配置
|
||||
sa-token:
|
||||
# SSO-Server 配置
|
||||
sso-server:
|
||||
# 主页路由:在 /sso/auth 登录页不指定 redirect 参数时,默认跳转的地址
|
||||
home-route: /home
|
||||
```
|
||||
<!------------- tab:properties 风格 ------------->
|
||||
``` properties
|
||||
# 主页路由:在 /sso/auth 登录页不指定 redirect 参数时,默认跳转的地址
|
||||
sa-token.sso-server.home-route: /home
|
||||
```
|
||||
<!---------------------------- tabs:end ---------------------------->
|
||||
|
||||
|
||||
|
||||
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 = "<h2>SSO 平台首页</h2>";
|
||||
String client1 = "<p><a href='" + link1 + "' target='_blank'> 进入Client1系统 </a></p>";
|
||||
String client2 = "<p><a href='" + link2 + "' target='_blank'> 进入Client2系统 </a></p>";
|
||||
String client3 = "<p><a href='" + link3 + "' target='_blank'> 进入Client3系统 </a></p>";
|
||||
|
||||
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 = "<h2>SSO 平台首页 (平台中心模式)</h2>";
|
||||
String client1 = "<p><a href='" + link1 + "' target='_blank'> 进入Client1系统 </a></p>";
|
||||
String client2 = "<p><a href='" + link2 + "' target='_blank'> 进入Client2系统 </a></p>";
|
||||
String client3 = "<p><a href='" + link3 + "' target='_blank'> 进入Client3系统 </a></p>";
|
||||
|
||||
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` 登录页,我们登录上之后,便会跳转到平台中心首页:
|
||||
|
||||
|
@@ -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 模式作为参考。
|
||||
|
||||
|
||||
|
||||
|
@@ -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<SaSsoClientModel> 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() {
|
||||
|
@@ -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 依赖的基础上,继续引入:
|
||||
|
||||
<!---------------------------- tabs:start ---------------------------->
|
||||
<!-------- tab:Maven 方式 -------->
|
||||
@@ -27,10 +27,10 @@
|
||||
<version>${sa.top.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
|
||||
<!-- Sa-Token 插件:整合 RedisTemplate -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-redis-jackson</artifactId>
|
||||
<artifactId>sa-token-redis-template</artifactId>
|
||||
<version>${sa.top.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -44,11 +44,11 @@
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Http请求工具(在模式三的单点注销功能下用到,如不需要可以注释掉) -->
|
||||
<!-- Sa-Token 插件:整合 Forest 请求工具 (模式三需要通过 http 请求推送消息) -->
|
||||
<dependency>
|
||||
<groupId>com.dtflys.forest</groupId>
|
||||
<artifactId>forest-spring-boot-starter</artifactId>
|
||||
<version>1.5.26</version>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-forest</artifactId>
|
||||
<version>${sa.top.version}</version>
|
||||
</dependency>
|
||||
```
|
||||
<!-------- tab:Gradle 方式 -------->
|
||||
@@ -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'
|
||||
```
|
||||
<!---------------------------- tabs:end ---------------------------->
|
||||
|
||||
@@ -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端尚未登录,请先访问"
|
||||
+ "<a href='/sso/doLogin?name=sa&pwd=123456' target='_blank'> doLogin登录 </a>"
|
||||
+ "进行登录之后,刷新页面开始授权";
|
||||
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 =
|
||||
"<h2>当前客户端在 SSO-Server 认证中心尚未登录,请先登录</h2>" +
|
||||
"用户:<input id='name' /> <br> " +
|
||||
"密码:<input id='pwd' /> <br>" +
|
||||
"<button onclick=\"" + doLoginCode + "\">登录</button>";
|
||||
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")`来获取。
|
||||
<!-- - `deviceId` 参数代表登录端设备id,是为了后续的 “单设备注销” 功能做准备,如果不需要此功能可以省略此参数。 -->
|
||||
|
||||
全局异常处理:
|
||||
``` 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
|
||||
```
|
||||
<!------------- tab:properties 风格 ------------->
|
||||
``` 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
|
||||
```
|
||||
|
||||
<!---------------------------- tabs:end ---------------------------->
|
||||
|
||||
注意点:`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 端点击登录按钮重定向而来。
|
||||
|
||||
|
||||
---
|
||||
|
@@ -58,7 +58,7 @@ sa-token.cookie.domain=stp.com
|
||||
```
|
||||
<!---------------------------- tabs:end ---------------------------->
|
||||
|
||||
这个配置原本是被注释掉的,现在将其打开。另外我们格外需要注意:
|
||||
**这个配置原本是被注释掉的,现在将其打开。**另外我们格外需要注意:
|
||||
在SSO模式一测试完毕之后,一定要将这个配置再次注释掉,因为模式一与模式二三使用不同的授权流程,这行配置会影响到我们模式二和模式三的正常运行。
|
||||
|
||||
|
||||
@@ -88,10 +88,10 @@ sa-token.cookie.domain=stp.com
|
||||
<version>${sa.top.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 整合redis (使用jackson序列化方式) -->
|
||||
<!-- Sa-Token 整合 RedisTemplate -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-redis-jackson</artifactId>
|
||||
<artifactId>sa-token-redis-template</artifactId>
|
||||
<version>${sa.top.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -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 = "<h2>Sa-Token SSO-Client 应用端</h2>" +
|
||||
"<p>当前会话是否登录:" + StpUtil.isLogin() + "</p>" +
|
||||
"<p><a href=\"javascript:location.href='" + authUrl + "?mode=simple&redirect=' + encodeURIComponent(location.href);\">登录</a> " +
|
||||
"<a href=\"javascript:location.href='" + solUrl + "?back=' + encodeURIComponent(location.href);\">注销</a> </p>";
|
||||
public String index(HttpServletRequest request) {
|
||||
String url = SaFoxUtil.encodeUrl( SaFoxUtil.joinParam(SaHolder.getRequest().getUrl(), request.getQueryString()) );
|
||||
SaSsoClientConfig cfg = SaSsoManager.getClientConfig();
|
||||
|
||||
String str = "<h2>Sa-Token SSO-Client 应用端 (模式一)</h2>" +
|
||||
"<p>当前会话是否登录:" + StpUtil.isLogin() + " (" + StpUtil.getLoginId("") + ")</p>" +
|
||||
"<p>" +
|
||||
"<a href='" + cfg.splicingAuthUrl() + "?mode=simple&client=" + cfg.getClient() + "&redirect=" + url + "'>登录</a> - " +
|
||||
"<a href='" + cfg.splicingSignoutUrl() + "?back=" + url + "'>注销</a> " +
|
||||
"</p>";
|
||||
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认证中心:
|
||||
|
||||

|
||||

|
||||
|
||||
我们点击登录,然后刷新页面:
|
||||
我们登录之后,然后刷新页面:
|
||||
|
||||

|
||||
|
||||
|
@@ -89,10 +89,10 @@ sa-token.cookie.domain=stp.com
|
||||
<version>${sa.top.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 整合redis (使用jackson序列化方式) -->
|
||||
<!-- Sa-Token 整合 RedisTemplate -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-redis-jackson</artifactId>
|
||||
<artifactId>sa-token-redis-template</artifactId>
|
||||
<version>${sa.top.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -106,6 +106,13 @@ sa-token.cookie.domain=stp.com
|
||||
<artifactId>sa-token-alone-redis</artifactId>
|
||||
<version>${sa.top.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 插件:整合 Forest 请求工具 -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-forest</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency>
|
||||
```
|
||||
<!-------- tab:Gradle 方式 -------->
|
||||
``` 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}'
|
||||
```
|
||||
<!---------------------------- tabs:end ---------------------------->
|
||||
|
||||
@@ -140,28 +150,58 @@ public class SsoClientController {
|
||||
// 首页
|
||||
@RequestMapping("/")
|
||||
public String index() {
|
||||
String solUrl = SaSsoManager.getClientConfig().splicingSloUrl();
|
||||
String str = "<h2>Sa-Token SSO-Client 应用端</h2>" +
|
||||
"<p>当前会话是否登录:" + StpUtil.isLogin() + "</p>" +
|
||||
"<p><a href=\"javascript:location.href='/sso/login?back=' + encodeURIComponent(location.href);\">登录</a> " +
|
||||
"<a href=\"javascript:location.href='" + solUrl + "?back=' + encodeURIComponent(location.href);\">注销</a> </p>";
|
||||
String str = "<h2>Sa-Token SSO-Client 应用端 (模式二)</h2>" +
|
||||
"<p>当前会话是否登录:" + StpUtil.isLogin() + " (" + StpUtil.getLoginId("") + ")</p>" +
|
||||
"<p> " +
|
||||
"<a href='/sso/login?back=/'>登录</a> - " +
|
||||
"<a href='/sso/logoutByAlone?back=/'>单应用注销</a> - " +
|
||||
"<a href='/sso/logout?back=self'>全端注销</a> - " +
|
||||
"<a href='/sso/myInfo' target='_blank'>账号资料</a>" +
|
||||
"</p>";
|
||||
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:
|
||||
<!------------- tab:properties 风格 ------------->
|
||||
``` 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,因此出现文字描述与截图端口号不一致情况,请注意甄别,后不再赘述) -->
|
||||
(注:先前版本文档测试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/)
|
||||
|
||||

|
||||
|
||||
|
@@ -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 机制,可以去除相关依赖:
|
||||
|
||||
<!---------------------------- tabs:start ---------------------------->
|
||||
<!-------- tab:Maven 方式 -------->
|
||||
``` xml
|
||||
<!-- Http请求工具 -->
|
||||
``` xml
|
||||
<!-- Sa-Token插件:权限缓存与业务缓存分离 -->
|
||||
<dependency>
|
||||
<groupId>com.dtflys.forest</groupId>
|
||||
<artifactId>forest-spring-boot-starter</artifactId>
|
||||
<version>1.5.26</version>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-alone-redis</artifactId>
|
||||
<version>${sa.top.version}</version>
|
||||
</dependency>
|
||||
```
|
||||
<!-------- tab:Gradle 方式 -------->
|
||||
``` 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}'
|
||||
```
|
||||
<!---------------------------- tabs:end ---------------------------->
|
||||
|
||||
Forest 是一个轻量级 http 请求工具,详情参考:[Forest](https://forest.dtflyx.com/)
|
||||
|
||||
#### 2.2、SSO-Client 端更改配置
|
||||
|
||||
#### 2.2、SSO-Client 端新增配置:API调用秘钥
|
||||
|
||||
在 `application.yml` 增加:
|
||||
更改 `application.yml` :
|
||||
|
||||
<!---------------------------- tabs:start ---------------------------->
|
||||
<!------------- tab:yaml 风格 ------------->
|
||||
``` 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
|
||||
```
|
||||
<!------------- tab:properties 风格 ------------->
|
||||
``` 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
|
||||
```
|
||||
<!---------------------------- tabs:end ---------------------------->
|
||||
|
||||
因为我们已经在控制台手动打印 url 请求日志了,所以此处 `forest.log-enabled=false` 关闭 Forest 框架自身的日志打印,这不是必须的,你可以将其打开。
|
||||
|
||||
注意 secretkey 秘钥需要与SSO认证中心的一致
|
||||
|
||||
<!--
|
||||
#### 2.3、SSO-Client 配置 http 请求处理器
|
||||
``` java
|
||||
// 配置SSO相关参数
|
||||
@@ -93,24 +135,24 @@ private void configSso(SaSsoClientConfig ssoClient) {
|
||||
return resStr;
|
||||
};
|
||||
}
|
||||
```
|
||||
``` -->
|
||||
|
||||
|
||||
#### 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中的数据清空,以防旧数据对测试造成干扰
|
||||
|
||||
测试步骤同模式二,不再赘述。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!--
|
||||
### 3、获取 UserInfo
|
||||
除了账号id,我们可能还需要将用户的昵称、头像等信息从 Server端 带到 Client端,即:用户资料的拉取。
|
||||
|
||||
@@ -252,71 +294,10 @@ public Object getFansList(Long loginId) {
|
||||
|
||||
#### 4.3、访问测试
|
||||
访问测试:[http://sa-sso-client1.com:9001/sso/myFansList](http://sa-sso-client1.com:9001/sso/myFansList)
|
||||
-->
|
||||
|
||||
|
||||
### 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. 整体完成。
|
||||
|
||||
|
||||
<button class="show-img" img-src="https://oss.dev33.cn/sa-token/doc/g/g3--sso3-logout.gif">加载动态演示图</button>
|
||||
|
||||
|
||||
这些逻辑 Sa-Token 内部已经封装完毕,你只需按照文档步骤集成即可。
|
||||
|
||||
#### 5.1、更改注销方案
|
||||
|
||||
将 sso-client 首页路由方法里的注销链接换成 `/sso/logout` 接口:
|
||||
``` java
|
||||
// SSO-Client端:首页
|
||||
@RequestMapping("/")
|
||||
public String index() {
|
||||
String str = "<h2>Sa-Token SSO-Client 应用端</h2>" +
|
||||
"<p>当前会话是否登录:" + StpUtil.isLogin() + "</p>" +
|
||||
"<p><a href=\"javascript:location.href='/sso/login?back=' + encodeURIComponent(location.href);\">登录</a>" +
|
||||
" <a href='/sso/logout?back=self'>注销</a></p>";
|
||||
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、后记
|
||||
当我们熟读三种模式的单点登录之后,其实不难发现:所谓单点登录,其本质就是多个系统之间的会话共享。
|
||||
|
||||
当我们理解这一点之后,三种模式的工作原理也浮出水面:
|
||||
|
@@ -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;
|
||||
}
|
||||
```
|
@@ -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;}
|
||||
|
Reference in New Issue
Block a user