简化单点登录集成步骤

This commit is contained in:
click33
2021-07-08 01:24:42 +08:00
parent 82f7d7f78c
commit 922e746eb1
33 changed files with 790 additions and 1136 deletions

View File

@@ -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、准备工作

View File

@@ -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();
}
``` -->

View File

@@ -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/)
我们主要的测试点在于 `单点注销`,正常登陆即可

View File

@@ -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