mirror of
https://gitee.com/dromara/sa-token.git
synced 2026-02-27 16:50:24 +08:00
简化单点登录集成步骤
This commit is contained in:
@@ -1,9 +1,6 @@
|
||||
# SSO模式一 共享Cookie同步会话
|
||||
|
||||
如果我们的系统可以保证部署在同一个主域名之下,并且后端连接同一个Redis,那么便可以使用 **`[共享Cookie同步会话]`** 的方式做到单点登录
|
||||
|
||||
> Sa-Token整合同域单点登录非常简单,相比于正常的登录,你只需增加配置 `sa-token.cookie-domain=xxx.com` 指定一下Cookie写入时的父级域名即可 <br>
|
||||
> 整合示例在官方仓库的 `/sa-token-demo/sa-token-demo-sso1/`,如遇到难点可结合源码进行测试学习
|
||||
如果我们的多个系统可以做到:前端同域、后端同Redis,那么便可以使用 **`[共享Cookie同步会话]`** 的方式做到单点登录
|
||||
|
||||
---
|
||||
|
||||
@@ -25,6 +22,9 @@
|
||||
|
||||
OK,所有理论就绪,下面开始实战
|
||||
|
||||
> Sa-Token整合同域单点登录非常简单,相比于正常的登录,你只需增加配置 `sa-token.cookie-domain=xxx.com` 指定一下Cookie写入时的父级域名即可 <br>
|
||||
> 整合示例在官方仓库的 `/sa-token-demo/sa-token-demo-sso1/`,如遇到难点可结合源码进行测试学习
|
||||
|
||||
|
||||
|
||||
### 1、准备工作
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# SSO模式二 URL重定向传播会话
|
||||
|
||||
如果我们的系统部署在不同的域名之下,但是后端可以连接同一个Redis,那么便可以使用 **`[URL重定向传播会话]`** 的方式做到单点登录
|
||||
如果我们的多个系统:部署在不同的域名之下,但是后端可以连接同一个Redis,那么便可以使用 **`[URL重定向传播会话]`** 的方式做到单点登录
|
||||
|
||||
|
||||
### 0、解题思路
|
||||
@@ -33,15 +33,9 @@
|
||||
> 搭建示例在官方仓库的 `/sa-token-demo/sa-token-demo-sso2-server/`,如遇到难点可结合源码进行测试学习
|
||||
|
||||
##### 1.1、创建SSO-Server端项目
|
||||
创建一个SpringBoot项目 `sa-token-demo-sso-server`(不会的同学自行百度或参考仓库示例),添加pom依赖:
|
||||
创建SpringBoot项目 `sa-token-demo-sso-server`(不会的同学自行百度或参考仓库示例),添加pom依赖:
|
||||
|
||||
``` xml
|
||||
<!-- SpringBoot依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
@@ -55,8 +49,6 @@
|
||||
<artifactId>sa-token-dao-redis-jackson</artifactId>
|
||||
<version>1.21.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 提供Redis连接池 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-pool2</artifactId>
|
||||
@@ -71,35 +63,33 @@
|
||||
@RestController
|
||||
public class SsoServerController {
|
||||
|
||||
// SSO-Server端:授权地址,跳转到登录页面
|
||||
@RequestMapping("ssoAuth")
|
||||
public Object ssoAuth(String redirect) {
|
||||
/*
|
||||
* 此处两种情况分开处理:
|
||||
* 1、如果在SSO认证中心尚未登录,则先去登登录
|
||||
* 2、如果在SSO认证中心尚已登录,则开始对redirect地址下放ticket引导授权
|
||||
*/
|
||||
// 情况1:尚未登录
|
||||
if(StpUtil.isLogin() == false) {
|
||||
String msg = "当前会话在SSO-Server端尚未登录,请先访问"
|
||||
+ "<a href='/doLogin?name=sa&pwd=123456' target='_blank'> doLogin登录 </a>"
|
||||
+ "进行登录之后,刷新页面开始授权";
|
||||
return msg;
|
||||
}
|
||||
// 情况2:已经登录,开始构建授权重定向地址,下放ticket
|
||||
String redirectUrl = SaSsoUtil.buildRedirectUrl(StpUtil.getLoginId(), redirect);
|
||||
return new ModelAndView("redirect:" + redirectUrl);
|
||||
// SSO-Server端:处理所有SSO相关请求
|
||||
@RequestMapping("/sso*")
|
||||
public Object ssoRequest() {
|
||||
return SaSsoHandle.serverRequest();
|
||||
}
|
||||
|
||||
// SSO-Server端:登录接口
|
||||
@RequestMapping("doLogin")
|
||||
public AjaxJson doLogin(String name, String pwd) {
|
||||
// 此处仅做模拟登录,真实环境应该查询数据进行登录
|
||||
if("sa".equals(name) && "123456".equals(pwd)) {
|
||||
StpUtil.login(10001);
|
||||
return AjaxJson.getSuccess("登录成功!");
|
||||
}
|
||||
return AjaxJson.getError("登录失败!");
|
||||
// 配置SSO相关参数
|
||||
@Autowired
|
||||
private void configSso(SaTokenConfig cfg) {
|
||||
cfg.sso
|
||||
// 配置:未登录时返回的View
|
||||
.setNotLoginView(() -> {
|
||||
String msg = "当前会话在SSO-Server端尚未登录,请先访问"
|
||||
+ "<a href='/ssoDoLogin?name=sa&pwd=123456' target='_blank'> doLogin登录 </a>"
|
||||
+ "进行登录之后,刷新页面开始授权";
|
||||
return msg;
|
||||
})
|
||||
// 配置:登录处理函数
|
||||
.setDoLoginHandle((name, pwd) -> {
|
||||
// 此处仅做模拟登录,真实环境应该查询数据进行登录
|
||||
if("sa".equals(name) && "123456".equals(pwd)) {
|
||||
StpUtil.login(10001);
|
||||
return SaResult.ok("登录成功!");
|
||||
}
|
||||
return SaResult.error("登录失败!");
|
||||
})
|
||||
;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -153,27 +143,19 @@ public class SaSsoServerApplication {
|
||||
##### 2.1、创建SSO-Client端项目
|
||||
创建一个SpringBoot项目 `sa-token-demo-sso-client`,添加pom依赖:
|
||||
``` xml
|
||||
<!-- SpringBoot依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot-starter</artifactId>
|
||||
<version>${sa-token-version}</version>
|
||||
<version>1.21.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 整合redis (使用jackson序列化方式) -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-dao-redis-jackson</artifactId>
|
||||
<version>${sa-token-version}</version>
|
||||
<version>1.21.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 提供Redis连接池 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-pool2</artifactId>
|
||||
@@ -202,42 +184,17 @@ public class SsoClientController {
|
||||
public String index() {
|
||||
String str = "<h2>Sa-Token SSO-Client 应用端</h2>" +
|
||||
"<p>当前会话是否登录:" + StpUtil.isLogin() + "</p>" +
|
||||
"<p><a href=\"javascript:location.href='/ssoLogin?back=' + encodeURIComponent(location.href);\">登录</a></p>";
|
||||
"<p><a href=\"javascript:location.href='/ssoLogin?back=' + encodeURIComponent(location.href);\">登录</a> " +
|
||||
"<a href='/ssoLogout' target='_blank'>注销</a></p>";
|
||||
return str;
|
||||
}
|
||||
|
||||
// SSO-Client端:登录地址
|
||||
@RequestMapping("ssoLogin")
|
||||
public Object ssoLogin(String back, String ticket) {
|
||||
// 如果当前Client端已经登录,则无需访问SSO认证中心,可以直接返回
|
||||
if(StpUtil.isLogin()) {
|
||||
return new ModelAndView("redirect:" + back);
|
||||
}
|
||||
/*
|
||||
* 接下来两种情况:
|
||||
* ticket无值,说明此请求是Client端访问,需要重定向至SSO认证中心
|
||||
* ticket有值,说明此请求从SSO认证中心重定向而来,需要根据ticket进行登录
|
||||
*/
|
||||
if(ticket == null) {
|
||||
String serverAuthUrl = SaSsoUtil.buildServerAuthUrl(SaHolder.getRequest().getUrl(), back);
|
||||
return new ModelAndView("redirect:" + serverAuthUrl);
|
||||
} else {
|
||||
Object loginId = checkTicket(ticket);
|
||||
if(loginId != null ) {
|
||||
// loginId有值,说明ticket有效
|
||||
StpUtil.login(loginId);
|
||||
return new ModelAndView("redirect:" + back);
|
||||
}
|
||||
// 此处向客户端提示ticket无效即可,不要重定向到SSO认证中心,否则容易引起无限重定向
|
||||
return "ticket无效: " + ticket;
|
||||
}
|
||||
// SSO-Client端:处理所有SSO相关请求
|
||||
@RequestMapping("/sso*")
|
||||
public Object ssoRequest() {
|
||||
return SaSsoHandle.clientRequest();
|
||||
}
|
||||
|
||||
// SSO-Client端:校验ticket,获取账号id
|
||||
private Object checkTicket(String ticket) {
|
||||
return SaSsoUtil.checkTicket(ticket);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
@@ -254,6 +211,8 @@ sa-token:
|
||||
sso:
|
||||
# SSO-Server端 单点登录地址
|
||||
auth-url: http://sa-sso-server.com:9000/ssoAuth
|
||||
# 是否打开单点注销接口
|
||||
is-slo: true
|
||||
|
||||
# 配置Sa-Token单独使用的Redis连接 (此处需要和SSO-Server端连接同一个Redis)
|
||||
alone-redis:
|
||||
@@ -385,13 +344,27 @@ Token作为长时间有效的会话凭证,在任何时候都不应该直接在
|
||||
因此Sa-Token-SSO选择先回传ticket,再由ticket获取账号id,且ticket一次性用完即废,提高安全性
|
||||
|
||||
|
||||
|
||||
### 6、跨Redis的单点登录
|
||||
以上流程解决了跨域模式下的单点登录,但是后端仍然采用了共享Redis来同步会话,如果我们的架构设计中Client端与Server端无法共享Redis,又该怎么完成单点登录?
|
||||
|
||||
这就要采用模式三了,且往下看:[Http请求获取会话](/sso/sso-type3)
|
||||
|
||||
|
||||
<!--
|
||||
### 6、如何单点注销?
|
||||
由于Server端与所有Client端都是在共用同一套会话,因此只要一端注销,即可全端下线,达到单点注销的效果
|
||||
|
||||
在`SsoClientController`中添加以下代码:
|
||||
``` java
|
||||
// SSO-Client端:单点注销 (所有端一起下线)
|
||||
@RequestMapping("logout")
|
||||
public SaResult logout() {
|
||||
StpUtil.logout();
|
||||
return SaResult.ok();
|
||||
}
|
||||
``` -->
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
|
||||
### 0、问题分析
|
||||
我们先来分析一下,当后端不使用共享Redis时,会对架构发生哪些影响
|
||||
我们先来分析一下,当后端不使用共享Redis时,会对架构产生哪些影响
|
||||
|
||||
1. Client端 无法直连 Redis 校验 ticket,取出账号id
|
||||
2. Client端 无法与 Server端 共用一套会话,需要自行维护子会话
|
||||
@@ -36,53 +36,37 @@
|
||||
> OkHttps是一个轻量级http请求工具,详情参考:[OkHttps](https://gitee.com/ejlchina-zhxu/okhttps)
|
||||
|
||||
##### 1.2、认证中心开放接口
|
||||
在SSO-Server端的`SsoServerController`中,新增以下接口:
|
||||
``` java
|
||||
// SSO-Server端:校验ticket 获取账号id
|
||||
@RequestMapping("checkTicket")
|
||||
public Object checkTicket(String ticket, String sloCallback) {
|
||||
// 校验ticket,获取对应的账号id
|
||||
Object loginId = SaSsoUtil.checkTicket(ticket);
|
||||
|
||||
// 注册此客户端的单点注销回调URL(不需要单点注销功能可删除此行代码)
|
||||
SaSsoUtil.registerSloCallbackUrl(loginId, sloCallback);
|
||||
|
||||
// 返回给Client端
|
||||
return loginId;
|
||||
}
|
||||
在SSO-Server端的`application.yml`中,新增以下配置:
|
||||
``` yml
|
||||
sa-token:
|
||||
sso:
|
||||
# 使用Http请求校验ticket
|
||||
is-http: true
|
||||
```
|
||||
此接口的作用是让Client端通过http请求校验ticket,获取对应的账号id
|
||||
此配置项的作用是开放ticket校验接口,让Client端通过http请求获取会话
|
||||
|
||||
##### 1.3、Client端新增配置
|
||||
在SSO-Client端的`SsoClientController`中,新增以下配置
|
||||
``` java
|
||||
// 配置SSO相关参数
|
||||
@Autowired
|
||||
private void configSso(SaTokenConfig cfg) {
|
||||
cfg.sso
|
||||
// 配置Http请求处理器
|
||||
.setSendHttp(url -> {
|
||||
return OkHttps.sync(url).get().getBody().toString();
|
||||
})
|
||||
;
|
||||
}
|
||||
```
|
||||
|
||||
``` yml
|
||||
sa-token:
|
||||
sso:
|
||||
# 使用Http请求校验ticket
|
||||
is-http: true
|
||||
# SSO-Server端 ticket校验地址
|
||||
check-ticket-url: http://sa-sso-server.com:9000/checkTicket
|
||||
```
|
||||
|
||||
##### 1.4、修改校验ticket的逻辑
|
||||
在模式二的`SsoClientController`中,校验ticket的方法是:
|
||||
``` java
|
||||
// SSO-Client端:校验ticket,获取账号id
|
||||
private Object checkTicket(String ticket) {
|
||||
return SaSsoUtil.checkTicket(ticket);
|
||||
}
|
||||
```
|
||||
不能直连Redis后,上述方法也将无效,我们把它改为以下方式:
|
||||
``` java
|
||||
// SSO-Client端:校验ticket码,获取对应的账号id
|
||||
private Object checkTicket(String ticket) {
|
||||
// 构建单点注销的回调URL(不需要单点注销时此值可填null )
|
||||
String sloCallback = SaHolder.getRequest().getUrl().replace("/ssoLogin", "/sloCallback");
|
||||
|
||||
// 使用OkHttps请求SSO-Server端,校验ticket
|
||||
String checkUrl = SaSsoUtil.buildCheckTicketUrl(ticket, sloCallback);
|
||||
String loginId = OkHttps.sync(checkUrl).get().getBody().toString();
|
||||
|
||||
// 判断返回值是否为有效账号Id
|
||||
return (SaFoxUtil.isEmpty(loginId) ? null : loginId);
|
||||
}
|
||||
check-ticket-url: http://sa-sso-server.com:9000/ssoCheckTicket
|
||||
```
|
||||
|
||||
##### 1.5 启动项目测试
|
||||
@@ -103,110 +87,48 @@ private Object checkTicket(String ticket) {
|
||||
5. Server端注销下线
|
||||
6. 单点注销完成
|
||||
|
||||
##### 2.1、SSO-Server认证中心增加单点注销接口
|
||||
新建 `SsoServerLogoutController` 增加以下代码
|
||||
##### 2.1、SSO-Server认证中心增加配置
|
||||
在 `SsoServerController` 中新增配置
|
||||
``` java
|
||||
/**
|
||||
* Sa-Token-SSO Server端 单点注销 Controller
|
||||
*/
|
||||
@RestController
|
||||
public class SsoServerLogoutController {
|
||||
|
||||
// SSO-Server端:单点注销
|
||||
@RequestMapping("ssoLogout")
|
||||
public String ssoLogout(String loginId, String secretkey) {
|
||||
|
||||
// 遍历通知Client端注销会话 (为了提高响应速度这里可将sync换为async)
|
||||
SaSsoUtil.singleLogout(secretkey, loginId, url -> OkHttps.sync(url).get());
|
||||
|
||||
// 完成
|
||||
return "ok";
|
||||
}
|
||||
|
||||
// 配置SSO相关参数
|
||||
@Autowired
|
||||
private void configSso(SaTokenConfig cfg) {
|
||||
cfg.sso
|
||||
// ... (其它配置保持不变)
|
||||
// 配置Http请求处理器
|
||||
.setSendHttp(url -> {
|
||||
// 此处为了提高响应速度这里可将sync换为async
|
||||
return OkHttps.sync(url).get();
|
||||
})
|
||||
;
|
||||
}
|
||||
```
|
||||
|
||||
并在 `application.yml` 下配置API调用秘钥
|
||||
并在 `application.yml` 下新增配置:
|
||||
``` yml
|
||||
sa-token:
|
||||
sso:
|
||||
# 打开单点注销功能
|
||||
is-slo: true
|
||||
# API调用秘钥(用于SSO模式三的单点注销功能)
|
||||
secretkey: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
|
||||
```
|
||||
|
||||
##### 2.2、SSO-Client端增加注销接口
|
||||
新建 `SsoClientLogoutController` 增加以下代码
|
||||
``` java
|
||||
/**
|
||||
* Sa-Token-SSO Client端 单点注销 Controller
|
||||
* @author kong
|
||||
*/
|
||||
@RestController
|
||||
public class SsoClientLogoutController {
|
||||
##### 2.2、SSO-Client端新增配置
|
||||
|
||||
// SSO-Client端:单端注销 (其它Client端会话不受影响)
|
||||
@RequestMapping("logout")
|
||||
public AjaxJson logout() {
|
||||
StpUtil.logout();
|
||||
return AjaxJson.getSuccess();
|
||||
}
|
||||
|
||||
// SSO-Client端:单点注销 (所有端一起下线)
|
||||
@RequestMapping("ssoLogout")
|
||||
public AjaxJson ssoLogout() {
|
||||
// 如果未登录,则无需注销
|
||||
if(StpUtil.isLogin() == false) {
|
||||
return AjaxJson.getSuccess();
|
||||
}
|
||||
// 调用SSO-Server认证中心API
|
||||
String url = SaSsoUtil.buildSloUrl(StpUtil.getLoginId());
|
||||
String res = OkHttps.sync(url).get().getBody().toString();
|
||||
if(res.equals("ok")) {
|
||||
return AjaxJson.getSuccess("单点注销成功");
|
||||
}
|
||||
return AjaxJson.getError("单点注销失败");
|
||||
}
|
||||
|
||||
// 单点注销的回调
|
||||
@RequestMapping("sloCallback")
|
||||
public String sloCallback(String loginId, String secretkey) {
|
||||
SaSsoUtil.checkSecretkey(secretkey);
|
||||
StpUtil.logoutByLoginId(loginId);
|
||||
return "ok";
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
!> `logoutByLoginId(id)`为踢人下线,如果要彻底清除数据,可更换为`StpUtil.logoutByTokenValue(StpUtil.getTokenValueByLoginId(loginId));`
|
||||
|
||||
并在 `application.yml` 增加配置: `API调用秘钥` 和 `单点注销接口URL`
|
||||
在 `application.yml` 增加配置:`API调用秘钥` 和 `单点注销接口URL`
|
||||
``` yml
|
||||
sa-token:
|
||||
sso:
|
||||
# SSO-Server端 单点注销地址
|
||||
# 打开单点注销功能
|
||||
is-slo: true
|
||||
# 单点注销地址
|
||||
slo-url: http://sa-sso-server.com:9000/ssoLogout
|
||||
# 接口调用秘钥(用于SSO模式三的单点注销功能)
|
||||
# 接口调用秘钥
|
||||
secretkey: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
|
||||
```
|
||||
|
||||
##### 2.3 更改Client端首页代码
|
||||
为了方便测试,我们更改一下Client端中`SsoClientController`类的`index`方法代码
|
||||
``` 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='/ssoLogin?back=' + encodeURIComponent(location.href);\">登录</a>" +
|
||||
" <a href='/ssoLogout' target='_blank'>注销</a></p>";
|
||||
return str;
|
||||
}
|
||||
```
|
||||
PS:相比于模式二,增加了单点注销的按钮
|
||||
|
||||
|
||||
##### 2.4 启动测试
|
||||
##### 2.3 启动测试
|
||||
启动SSO-Server、SSO-Client,访问测试:[http://sa-sso-client1.com:9001/](http://sa-sso-client1.com:9001/),
|
||||
我们主要的测试点在于 `单点注销`,正常登陆即可
|
||||
|
||||
|
||||
@@ -104,6 +104,7 @@ PS:两者的区别在于:**`方式1会覆盖yml中的配置,方式2会与y
|
||||
| authUrl | String | null | SSO-Server端 单点登录地址 |
|
||||
| checkTicketUrl| String | null | SSO-Server端 Ticket校验地址 |
|
||||
| sloUrl | String | null | SSO-Server端 单点注销地址 |
|
||||
| ssoLogoutCall | String | null | SSO-Client端 当前Client端的单点注销回调URL (为空时自动获取) |
|
||||
|
||||
配置示例:
|
||||
``` yml
|
||||
|
||||
Reference in New Issue
Block a user