mirror of
https://gitee.com/dromara/sa-token.git
synced 2025-09-18 17:48:03 +08:00
优化SSO相关文档
This commit is contained in:
@@ -15,13 +15,13 @@ sa-token:
|
||||
# Redis数据库索引(默认为0)
|
||||
database: 2
|
||||
# Redis服务器地址
|
||||
host: 127.0.0.1
|
||||
host: 49.235.117.153
|
||||
# Redis服务器连接端口
|
||||
port: 6379
|
||||
# Redis服务器连接密码(默认为空)
|
||||
password:
|
||||
password: Kdfsjia.d.1212dsa
|
||||
# 连接超时时间(毫秒)
|
||||
timeout: 10ms
|
||||
timeout: 10s
|
||||
lettuce:
|
||||
pool:
|
||||
# 连接池最大连接数
|
||||
@@ -45,7 +45,7 @@ spring:
|
||||
# Redis服务器连接密码(默认为空)
|
||||
password:
|
||||
# 连接超时时间(毫秒)
|
||||
timeout: 10ms
|
||||
timeout: 10s
|
||||
lettuce:
|
||||
pool:
|
||||
# 连接池最大连接数
|
||||
|
@@ -30,8 +30,8 @@ spring:
|
||||
port: 6379
|
||||
# Redis服务器连接密码(默认为空)
|
||||
password:
|
||||
# 连接超时时间(毫秒)
|
||||
timeout: 10ms
|
||||
# 连接超时时间
|
||||
timeout: 10s
|
||||
lettuce:
|
||||
pool:
|
||||
# 连接池最大连接数
|
||||
|
@@ -21,8 +21,8 @@ sa-token:
|
||||
port: 6379
|
||||
# Redis服务器连接密码(默认为空)
|
||||
password:
|
||||
# 连接超时时间(毫秒)
|
||||
timeout: 10ms
|
||||
# 连接超时时间
|
||||
timeout: 10s
|
||||
lettuce:
|
||||
pool:
|
||||
# 连接池最大连接数
|
||||
|
@@ -23,8 +23,8 @@ spring:
|
||||
port: 6379
|
||||
# Redis服务器连接密码(默认为空)
|
||||
password:
|
||||
# 连接超时时间(毫秒)
|
||||
timeout: 10ms
|
||||
# 连接超时时间
|
||||
timeout: 10s
|
||||
lettuce:
|
||||
pool:
|
||||
# 连接池最大连接数
|
||||
|
@@ -32,8 +32,8 @@ spring:
|
||||
port: 6379
|
||||
# Redis服务器连接密码(默认为空)
|
||||
password:
|
||||
# 连接超时时间(毫秒)
|
||||
timeout: 10ms
|
||||
# 连接超时时间
|
||||
timeout: 10s
|
||||
lettuce:
|
||||
pool:
|
||||
# 连接池最大连接数
|
||||
|
@@ -29,8 +29,8 @@ spring:
|
||||
port: 6379
|
||||
# Redis服务器连接密码(默认为空)
|
||||
password:
|
||||
# 连接超时时间(毫秒)
|
||||
timeout: 10ms
|
||||
# 连接超时时间
|
||||
timeout: 10s
|
||||
lettuce:
|
||||
pool:
|
||||
# 连接池最大连接数
|
||||
|
@@ -19,7 +19,7 @@
|
||||
为了保证新同学不迷路,请允许我唠叨一下:无论您从何处看到本篇文章,最新开发文档永远在:[http://sa-token.dev33.cn/](http://sa-token.dev33.cn/),
|
||||
建议收藏在浏览器书签,如果您已经身处本网站下,则请忽略此条说明。
|
||||
|
||||
用心阅读文档,你学习到的将不止是 `Sa-Token` 框架本身,更是绝大多数场景下权限设计的最佳实践。
|
||||
本文档将会尽力讲解每个功能的设计原因、应用场景,用心阅读文档,你学习到的将不止是 `Sa-Token` 框架本身,更是绝大多数场景下权限设计的最佳实践。
|
||||
|
||||
## Sa-Token 介绍
|
||||
**Sa-Token** 是一个轻量级 Java 权限认证框架,主要解决:**`登录认证`**、**`权限认证`**、**`Session会话`**、**`单点登录`**、**`OAuth2.0`**、**`微服务网关鉴权`**
|
||||
|
@@ -36,7 +36,11 @@
|
||||
- [SSO模式一 共享Cookie同步会话](/sso/sso-type1)
|
||||
- [SSO模式二 URL重定向传播会话](/sso/sso-type2)
|
||||
- [SSO模式三 Http请求获取会话](/sso/sso-type3)
|
||||
- [SSO整合-常见问题总结](/sso/sso-cd)
|
||||
- [SSO整合:配置域名校验](/sso/sso-check-domain)
|
||||
- [SSO整合:定制化登录页面](/sso/sso-custom-login)
|
||||
- [SSO整合:前后端分离下的整合方案](/sso/sso-h5)
|
||||
- [SSO整合:常见问题总结](/sso/sso-questions)
|
||||
- [Sa-Sso-Pro:单点登录商业版](/sso/sso-pro)
|
||||
|
||||
- **OAuth2.0**
|
||||
- [OAuth2.0简述](/oauth2/readme)
|
||||
|
@@ -71,7 +71,7 @@
|
||||
// basePath: '/sa-token-doc/', // 设置文件加载的父路径, 这在一些带项目名部署的文件中非常有效
|
||||
auto2top: true, // 是否在切换页面后回到顶部
|
||||
// coverpage: true, // 开启封面
|
||||
subMaxLevel: 3, // 标题解析层级, 写几就在目录树中解析到几级标题 ,一般写2吧也就
|
||||
subMaxLevel: 4, // 标题解析层级, 写几就在目录树中解析到几级标题 ,一般写2吧也就
|
||||
loadSidebar: true, // 加载自定义侧边栏 , 目录定制在: _sidebar.md 文件 (需要创建 .nojekyll 的空文件,阻止 GitHub Pages 忽略命名是下划线开头的文件)
|
||||
copyCode: { // 复制插件
|
||||
buttonText: '复制到剪贴板',
|
||||
|
@@ -8,6 +8,7 @@ body{font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu
|
||||
#main h3 {font-size: 1.25rem;}
|
||||
|
||||
.main-box .markdown-section{ padding: 30px 20px; max-width: 75%; margin-left: 10%;}
|
||||
.main-box .markdown-section h4{font-size: 1rem;}
|
||||
@media screen and (max-width: 800px) {
|
||||
.logo-box {display: none;}
|
||||
.main-box .markdown-section{max-width: 1000px; margin-left: auto;}
|
||||
|
@@ -33,7 +33,7 @@ sa-token:
|
||||
# Token风格
|
||||
token-style: uuid
|
||||
|
||||
# 配置Sa-Token单独使用的Redis连接
|
||||
# 配置 Sa-Token 单独使用的 Redis 连接
|
||||
alone-redis:
|
||||
# Redis数据库索引(默认为0)
|
||||
database: 2
|
||||
@@ -43,11 +43,11 @@ sa-token:
|
||||
port: 6379
|
||||
# Redis服务器连接密码(默认为空)
|
||||
password:
|
||||
# 连接超时时间(毫秒)
|
||||
timeout: 10ms
|
||||
# 连接超时时间
|
||||
timeout: 10s
|
||||
|
||||
spring:
|
||||
# 配置业务使用的Redis连接
|
||||
# 配置业务使用的 Redis 连接
|
||||
redis:
|
||||
# Redis数据库索引(默认为0)
|
||||
database: 0
|
||||
@@ -57,8 +57,8 @@ spring:
|
||||
port: 6379
|
||||
# Redis服务器连接密码(默认为空)
|
||||
password:
|
||||
# 连接超时时间(毫秒)
|
||||
timeout: 10ms
|
||||
# 连接超时时间
|
||||
timeout: 10s
|
||||
```
|
||||
|
||||
具体可参考示例:[码云:application.yml](https://gitee.com/dromara/sa-token/blob/dev/sa-token-demo/sa-token-demo-alone-redis/src/main/resources/application.yml)
|
||||
|
@@ -1,12 +0,0 @@
|
||||
<!-- 这是目录树文件 -->
|
||||
|
||||
- **单点登录**
|
||||
- [单点登录简述](/sso/readme)
|
||||
- [SSO模式一 共享Cookie同步会话](/sso/sso-type1)
|
||||
- [SSO模式二 URL重定向传播会话](/sso/sso-type2)
|
||||
- [SSO模式三 Http请求获取会话](/sso/sso-type3)
|
||||
|
||||
|
||||
|
||||
<!-- <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
|
||||
<p style="text-align: center; ">----- 到底线了 -----</p> -->
|
@@ -1,263 +0,0 @@
|
||||
# Sa-Token-SSO整合-常见问题总结
|
||||
|
||||
---
|
||||
|
||||
|
||||
### 一、何时引导用户去登录?
|
||||
|
||||
以下方案三选一:
|
||||
|
||||
##### 1.1、前端按钮跳转
|
||||
前端页面准备一个**`[登录]`**按钮,当用户点击按钮时,跳转到登录接口
|
||||
``` js
|
||||
<a href="javascript:location.href='/sso/login?back=' + encodeURIComponent(location.href);">登录</a>
|
||||
```
|
||||
|
||||
##### 1.2、后端拦截重定向
|
||||
在后端注册全局过滤器(或拦截器、或全局异常处理),拦截需要登录后才能访问的页面资源,将未登录的访问重定向至登录接口
|
||||
``` java
|
||||
/**
|
||||
* Sa-Token 配置类
|
||||
*/
|
||||
@Configuration
|
||||
public class SaTokenConfigure implements WebMvcConfigurer {
|
||||
/** 注册 [Sa-Token全局过滤器] */
|
||||
@Bean
|
||||
public SaServletFilter getSaServletFilter() {
|
||||
return new SaServletFilter()
|
||||
.addInclude("/**")
|
||||
.addExclude("/sso/*", "/favicon.ico")
|
||||
.setAuth(r -> {
|
||||
if(StpUtil.isLogin() == false) {
|
||||
String back = SaFoxUtil.joinParam(SaHolder.getRequest().getUrl(), SpringMVCUtil.getRequest().getQueryString());
|
||||
SaHolder.getResponse().redirect("/sso/login?back=" + SaFoxUtil.encodeUrl(back));
|
||||
SaRouter.back();
|
||||
}
|
||||
})
|
||||
;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### 1.3、后端拦截 + 前端跳转
|
||||
首先,后端仍需要提供拦截,但是不直接引导用户重定向,而是返回未登录的提示信息
|
||||
``` java
|
||||
/**
|
||||
* Sa-Token 配置类
|
||||
*/
|
||||
@Configuration
|
||||
public class SaTokenConfigure implements WebMvcConfigurer {
|
||||
/** 注册 [Sa-Token全局过滤器] */
|
||||
@Bean
|
||||
public SaServletFilter getSaServletFilter() {
|
||||
return new SaServletFilter()
|
||||
.addInclude("/**")
|
||||
.addExclude("/sso/*", "/favicon.ico")
|
||||
.setAuth(r -> {
|
||||
if(StpUtil.isLogin() == false) {
|
||||
// 与前端约定好,code=401时代表会话未登录
|
||||
SaRouter.back(SaResult.ok().setCode(401));
|
||||
}
|
||||
})
|
||||
;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
前端接受到返回结果 `code=401` 时,开始跳转至登录接口
|
||||
``` js
|
||||
if(res.code == 401) {
|
||||
location.href = '/sso/login?back=' + encodeURIComponent(location.href);
|
||||
}
|
||||
```
|
||||
|
||||
这种方案比较适合以 Ajax 访问的 RestAPI 接口重定向
|
||||
|
||||
|
||||
|
||||
|
||||
### 二、如何自定义登录视图?
|
||||
|
||||
- 方式一:在demo示例中直接更改页面代码
|
||||
- 方式二:在配置中配置登录视图地址
|
||||
|
||||
``` java
|
||||
cfg.sso
|
||||
// 配置:未登录时返回的View
|
||||
.setNotLoginView(() -> {
|
||||
return new ModelAndView("xxx.html");
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
### 三、如何自定义登录API的接口?
|
||||
根据需求点选择解决方案:
|
||||
|
||||
##### 3.1、如果只是想在 setDoLoginHandle 函数里获取除 name、pwd 以外的参数?
|
||||
``` java
|
||||
// 在任意代码处获取前端提交的参数
|
||||
String xxx = SaHolder.getRequest().getParam("xxx");
|
||||
```
|
||||
|
||||
##### 3.2、想完全自定义一个接口来接受前端登录请求?
|
||||
``` java
|
||||
// 直接定义一个拦截路由为 `/sso/doLogin` 的接口即可
|
||||
@RequestMapping("/sso/doLogin")
|
||||
public SaResult ss(String name, String pwd) {
|
||||
System.out.println("------ 请求进入了自定义的API接口 ---------- ");
|
||||
if("sa".equals(name) && "123456".equals(pwd)) {
|
||||
StpUtil.login(10001);
|
||||
return SaResult.ok("登录成功!");
|
||||
}
|
||||
return SaResult.error("登录失败!");
|
||||
}
|
||||
```
|
||||
|
||||
##### 3.3、不想使用`/sso/doLogin`这个接口,想自定义一个API地址?
|
||||
|
||||
答:直接在前端更改点击按钮时 Ajax 的请求地址即可
|
||||
|
||||
|
||||
### 四、前后端分离架构下的整合方案
|
||||
|
||||
如果我们已有的系统是前后端分离模式,我们显然不能为了接入SSO而改造系统的基础架构,官方仓库的示例采用的是前后端一体方案,要将其改造为前后台分离架构模式非常简单
|
||||
|
||||
以`sa-token-demo-sso2-client`为例:
|
||||
|
||||
##### 4.1、新建`H5Controller`开放接口
|
||||
``` java
|
||||
/**
|
||||
* 前后台分离架构下集成SSO所需的代码
|
||||
*/
|
||||
@RestController
|
||||
public class H5Controller {
|
||||
|
||||
// 当前是否登录
|
||||
@RequestMapping("/isLogin")
|
||||
public Object isLogin() {
|
||||
return SaResult.data(StpUtil.isLogin());
|
||||
}
|
||||
|
||||
// 返回SSO认证中心登录地址
|
||||
@RequestMapping("/getSsoAuthUrl")
|
||||
public SaResult getSsoAuthUrl(String clientLoginUrl) {
|
||||
String serverAuthUrl = SaSsoUtil.buildServerAuthUrl(clientLoginUrl, "");
|
||||
return SaResult.data(serverAuthUrl);
|
||||
}
|
||||
|
||||
// 根据ticket进行登录
|
||||
@RequestMapping("/doLoginByTicket")
|
||||
public SaResult doLoginByTicket(String ticket) {
|
||||
Object loginId = checkTicket(ticket);
|
||||
if(loginId != null) {
|
||||
StpUtil.login(loginId);
|
||||
return SaResult.data(StpUtil.getTokenValue());
|
||||
}
|
||||
return SaResult.error("无效ticket:" + ticket);
|
||||
}
|
||||
|
||||
// 校验 Ticket码,获取账号Id
|
||||
private Object checkTicket(String ticket) {
|
||||
return SaSsoUtil.checkTicket(ticket);
|
||||
}
|
||||
|
||||
// 全局异常拦截
|
||||
@ExceptionHandler
|
||||
public SaResult handlerException(Exception e) {
|
||||
e.printStackTrace();
|
||||
return SaResult.error(e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
##### 4.2、增加跨域过滤器`CorsFilter.java`
|
||||
源码详见:[CorsFilter.java](https://gitee.com/dromara/sa-token/tree/master/sa-token-demo/sa-token-demo-sso2-client/src/main/java/com/pj/h5/CorsFilter.java),
|
||||
将其复制到项目中即可
|
||||
|
||||
##### 4.3、新建前端项目
|
||||
任意文件夹新建前端项目:`sa-token-demo-sso2-client-h5`,在根目录添加测试文件:`index.html`
|
||||
``` js
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Sa-Token-SSO-Client端-测试页(前后端分离版)</title>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Sa-Token SSO-Client 应用端(前后端分离版)</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>
|
||||
</p>
|
||||
<script src="https://unpkg.zhimg.com/jquery@3.4.1/dist/jquery.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
// 后端接口地址
|
||||
var baseUrl = "http://sa-sso-client1.com:9001";
|
||||
|
||||
// 查询当前会话是否登录
|
||||
$.ajax({
|
||||
url: baseUrl + '/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));
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
##### 4.4、添加登录处理文件`sso-login.html`
|
||||
源码详见:[sso-login.html](https://gitee.com/dromara/sa-token/tree/master/sa-token-demo/sa-token-demo-sso2-client-h5/sso-login.html),
|
||||
将其复制到项目中即可,与`index.html`一样放在根目录下
|
||||
|
||||
|
||||
##### 4.5、测试运行
|
||||
先启动Server服务端与Client服务端,再随便找个能预览html的工具打开前端项目(比如[HBuilderX](https://www.dcloud.io/hbuilderx.html)),测试流程与一体版一致
|
||||
|
||||
##### 4.6、疑问:我在SSO模式三的demo中加入上述代码,提示我ticket无效,是怎么回事?
|
||||
上述代码是以SSO模式二为基础的,提示“Ticket无效”的原因很简单,因为SSO模式三种 Server端 与 Client端 连接的不是同一个Redis,
|
||||
所以Client端校验Ticket时无法在Redis中查询到相应的值,才会产生异常:“Ticket无效”
|
||||
|
||||
要使上述代码生效很简单,我们只需更改一下校验Ticket的逻辑即可,将 `H5Controller` 中的 `checkTicket` 方法代码改为:
|
||||
``` java
|
||||
// 校验 Ticket码,获取账号Id
|
||||
private Object checkTicket(String ticket) {
|
||||
SaSsoConfig cfg = SaManager.getConfig().getSso();
|
||||
String ssoLogoutCall = null;
|
||||
if(cfg.isSlo) {
|
||||
ssoLogoutCall = SaHolder.getRequest().getUrl().replace("/doLoginByTicket", Api.ssoLogoutCall);
|
||||
}
|
||||
String checkUrl = SaSsoUtil.buildCheckTicketUrl(ticket, ssoLogoutCall);
|
||||
Object body = cfg.sendHttp.apply(checkUrl);
|
||||
return (SaFoxUtil.isEmpty(body) ? null : body);
|
||||
}
|
||||
```
|
||||
|
||||
重新运行项目,即可在SSO模式三中成功整合前后台分离模式 。
|
||||
|
||||
|
||||
|
||||
|
||||
### 五、常见疑问
|
||||
|
||||
##### 问:在模式一与模式二中,Client端 必须通过 Alone-Redis 插件来访问Redis吗?
|
||||
答:不必须,只是推荐,权限缓存与业务缓存分离后会减少SSO-Redis的访问压力,且可以避免多个Client端的缓存读写冲突
|
||||
|
||||
##### 问:将旧有系统改造为单点登录时,应该注意哪些?
|
||||
建议不要把其中一个系统改造为SSO服务端,而是新起一个项目作为Server端,所有旧有项目全部作为Client端与此对接
|
||||
|
||||
|
||||
|
43
sa-token-doc/doc/sso/sso-check-domain.md
Normal file
43
sa-token-doc/doc/sso/sso-check-domain.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# SSO整合-配置域名校验
|
||||
|
||||
---
|
||||
|
||||
### 1、Ticket劫持攻击
|
||||
在前面章节的 SSO-Server 示例中,配置项 `sa-token.sso.allow-url=*` 意为配置所有允许的Client端授权地址,不在此配置项中的URL将无法单点登录成功
|
||||
|
||||
为了方便测试,上述代码将其配置为`*`,但是,<font color="#FF0000" >在生产环境中,此配置项绝对不能配置为 * </font>,否则会有被 Ticket 劫持的风险
|
||||
|
||||
假设攻击者根据模仿我们的授权地址,巧妙的构造一个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/)
|
||||
|
||||
当不知情的小红被诱导访问了这个URL时,它将被重定向至百度首页
|
||||
|
||||

|
||||
|
||||
可以看到,代表着用户身份的 Ticket 码也显现到了URL之中,借此漏洞,攻击者完全可以构建一个URL将小红的 Ticket 码自动提交到攻击者自己的服务器,伪造小红身份登录网站
|
||||
|
||||
### 2、防范方法
|
||||
|
||||
造成此漏洞的直接原因就是SSO-Server认证中心没有对 `redirect地址` 进行任何的限制,防范的方法也很简单,就是对`redirect参数`进行校验,如果其不在指定的URL列表中时,拒绝下放ticket
|
||||
|
||||
我们将其配置为一个具体的URL:`allow-url=http://sa-sso-client1.com:9001/sso/login`,再次访问上述连接:
|
||||
|
||||

|
||||
|
||||
域名没有通过校验,拒绝授权!
|
||||
|
||||
### 3、配置安全性参考表
|
||||
|
||||
| 配置方式 | 举例 | 安全性 | 建议 |
|
||||
| :-------- | :-------- | :-------- | :-------- |
|
||||
| 配置为* | `*` | <font color="#F00" >低</font> | **<font color="#F00" >禁止在生产环境下使用</font>** |
|
||||
| 配置到域名 | `http://sa-sso-client1.com/*` | <font color="#F70" >中</font> | <font color="#F70" >不建议在生产环境下使用</font> |
|
||||
| 配置到详细地址| `http://sa-sso-client1.com:9001/sso/login` | <font color="#080" >高</font> | <font color="#080" >可以在生产环境下使用</font> |
|
||||
|
||||
|
||||
### 4、疑问:为什么不直接回传 Token,而是先回传 Ticket,再用 Ticket 去查询对应的账号id?
|
||||
Token 作为长时间有效的会话凭证,在任何时候都不应该直接暴露在 URL 之中(虽然 Token 直接的暴露本身不会造成安全漏洞,但会为很多漏洞提供可乘之机)
|
||||
|
||||
因此 Sa-Token-SSO 选择先回传 Ticket,再由 Ticket 获取账号id,且 Ticket 一次性用完即废,提高安全性
|
||||
|
117
sa-token-doc/doc/sso/sso-custom-login.md
Normal file
117
sa-token-doc/doc/sso/sso-custom-login.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# SSO整合-定制化登录页面
|
||||
|
||||
---
|
||||
|
||||
### 1、何时引导用户去登录?
|
||||
|
||||
#### 方案一:前端按钮跳转
|
||||
前端页面准备一个**`[登录]`**按钮,当用户点击按钮时,跳转到登录接口
|
||||
``` js
|
||||
<a href="javascript:location.href='/sso/login?back=' + encodeURIComponent(location.href);">登录</a>
|
||||
```
|
||||
|
||||
#### 方案二:后端拦截重定向
|
||||
在后端注册全局过滤器(或拦截器、或全局异常处理),拦截需要登录后才能访问的页面资源,将未登录的访问重定向至登录接口
|
||||
``` java
|
||||
/**
|
||||
* Sa-Token 配置类
|
||||
*/
|
||||
@Configuration
|
||||
public class SaTokenConfigure implements WebMvcConfigurer {
|
||||
/** 注册 [Sa-Token全局过滤器] */
|
||||
@Bean
|
||||
public SaServletFilter getSaServletFilter() {
|
||||
return new SaServletFilter()
|
||||
.addInclude("/**")
|
||||
.addExclude("/sso/*", "/favicon.ico")
|
||||
.setAuth(r -> {
|
||||
if(StpUtil.isLogin() == false) {
|
||||
String back = SaFoxUtil.joinParam(SaHolder.getRequest().getUrl(), SpringMVCUtil.getRequest().getQueryString());
|
||||
SaHolder.getResponse().redirect("/sso/login?back=" + SaFoxUtil.encodeUrl(back));
|
||||
SaRouter.back();
|
||||
}
|
||||
})
|
||||
;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 方案三:后端拦截 + 前端跳转
|
||||
首先,后端仍需要提供拦截,但是不直接引导用户重定向,而是返回未登录的提示信息
|
||||
``` java
|
||||
/**
|
||||
* Sa-Token 配置类
|
||||
*/
|
||||
@Configuration
|
||||
public class SaTokenConfigure implements WebMvcConfigurer {
|
||||
/** 注册 [Sa-Token全局过滤器] */
|
||||
@Bean
|
||||
public SaServletFilter getSaServletFilter() {
|
||||
return new SaServletFilter()
|
||||
.addInclude("/**")
|
||||
.addExclude("/sso/*", "/favicon.ico")
|
||||
.setAuth(r -> {
|
||||
if(StpUtil.isLogin() == false) {
|
||||
// 与前端约定好,code=401时代表会话未登录
|
||||
SaRouter.back(SaResult.ok().setCode(401));
|
||||
}
|
||||
})
|
||||
;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
前端接受到返回结果 `code=401` 时,开始跳转至登录接口
|
||||
``` js
|
||||
if(res.code == 401) {
|
||||
location.href = '/sso/login?back=' + encodeURIComponent(location.href);
|
||||
}
|
||||
```
|
||||
|
||||
这种方案比较适合以 Ajax 访问的 RestAPI 接口重定向
|
||||
|
||||
|
||||
|
||||
|
||||
### 2、如何自定义登录视图?
|
||||
|
||||
#### 方式一:在demo示例中直接更改 login.html 页面代码即可
|
||||
|
||||
#### 方式二:在配置中配置登录视图地址
|
||||
|
||||
``` java
|
||||
cfg.sso
|
||||
// 配置:未登录时返回的View
|
||||
.setNotLoginView(() -> {
|
||||
return new ModelAndView("xxx.html");
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
### 3、如何自定义登录API的接口?
|
||||
根据需求点选择解决方案:
|
||||
|
||||
#### 3.1、如果只是想在 setDoLoginHandle 函数里获取除 name、pwd 以外的参数?
|
||||
``` java
|
||||
// 在任意代码处获取前端提交的参数
|
||||
String xxx = SaHolder.getRequest().getParam("xxx");
|
||||
```
|
||||
|
||||
#### 3.2、想完全自定义一个接口来接受前端登录请求?
|
||||
``` java
|
||||
// 直接定义一个拦截路由为 `/sso/doLogin` 的接口即可
|
||||
@RequestMapping("/sso/doLogin")
|
||||
public SaResult ss(String name, String pwd) {
|
||||
System.out.println("------ 请求进入了自定义的API接口 ---------- ");
|
||||
if("sa".equals(name) && "123456".equals(pwd)) {
|
||||
StpUtil.login(10001);
|
||||
return SaResult.ok("登录成功!");
|
||||
}
|
||||
return SaResult.error("登录失败!");
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.3、不想使用`/sso/doLogin`这个接口,想自定义一个API地址?
|
||||
|
||||
答:直接在前端更改点击按钮时 Ajax 的请求地址即可
|
||||
|
135
sa-token-doc/doc/sso/sso-h5.md
Normal file
135
sa-token-doc/doc/sso/sso-h5.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# SSO整合-前后端分离架构下的整合方案
|
||||
|
||||
---
|
||||
|
||||
如果我们已有的系统是前后端分离模式,我们显然不能为了接入SSO而改造系统的基础架构,官方仓库的示例采用的是前后端一体方案,要将其改造为前后台分离架构模式非常简单
|
||||
|
||||
以`sa-token-demo-sso2-client`为例:
|
||||
|
||||
### 1、新建`H5Controller`开放接口
|
||||
``` java
|
||||
/**
|
||||
* 前后台分离架构下集成SSO所需的代码
|
||||
*/
|
||||
@RestController
|
||||
public class H5Controller {
|
||||
|
||||
// 当前是否登录
|
||||
@RequestMapping("/isLogin")
|
||||
public Object isLogin() {
|
||||
return SaResult.data(StpUtil.isLogin());
|
||||
}
|
||||
|
||||
// 返回SSO认证中心登录地址
|
||||
@RequestMapping("/getSsoAuthUrl")
|
||||
public SaResult getSsoAuthUrl(String clientLoginUrl) {
|
||||
String serverAuthUrl = SaSsoUtil.buildServerAuthUrl(clientLoginUrl, "");
|
||||
return SaResult.data(serverAuthUrl);
|
||||
}
|
||||
|
||||
// 根据ticket进行登录
|
||||
@RequestMapping("/doLoginByTicket")
|
||||
public SaResult doLoginByTicket(String ticket) {
|
||||
Object loginId = checkTicket(ticket);
|
||||
if(loginId != null) {
|
||||
StpUtil.login(loginId);
|
||||
return SaResult.data(StpUtil.getTokenValue());
|
||||
}
|
||||
return SaResult.error("无效ticket:" + ticket);
|
||||
}
|
||||
|
||||
// 校验 Ticket码,获取账号Id
|
||||
private Object checkTicket(String ticket) {
|
||||
return SaSsoUtil.checkTicket(ticket);
|
||||
}
|
||||
|
||||
// 全局异常拦截
|
||||
@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-sso2-client/src/main/java/com/pj/h5/CorsFilter.java),
|
||||
将其复制到项目中即可
|
||||
|
||||
### 3、新建前端项目
|
||||
任意文件夹新建前端项目:`sa-token-demo-sso2-client-h5`,在根目录添加测试文件:`index.html`
|
||||
``` js
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Sa-Token-SSO-Client端-测试页(前后端分离版)</title>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Sa-Token SSO-Client 应用端(前后端分离版)</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>
|
||||
</p>
|
||||
<script src="https://unpkg.zhimg.com/jquery@3.4.1/dist/jquery.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
// 后端接口地址
|
||||
var baseUrl = "http://sa-sso-client1.com:9001";
|
||||
|
||||
// 查询当前会话是否登录
|
||||
$.ajax({
|
||||
url: baseUrl + '/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));
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### 4、添加登录处理文件`sso-login.html`
|
||||
源码详见:[sso-login.html](https://gitee.com/dromara/sa-token/tree/master/sa-token-demo/sa-token-demo-sso2-client-h5/sso-login.html),
|
||||
将其复制到项目中即可,与`index.html`一样放在根目录下
|
||||
|
||||
|
||||
### 5、测试运行
|
||||
先启动Server服务端与Client服务端,再随便找个能预览html的工具打开前端项目(比如[HBuilderX](https://www.dcloud.io/hbuilderx.html)),测试流程与一体版一致
|
||||
|
||||
### 6、疑问:我在SSO模式三的demo中加入上述代码,提示我ticket无效,是怎么回事?
|
||||
上述代码是以SSO模式二为基础的,提示“Ticket无效”的原因很简单,因为SSO模式三种 Server端 与 Client端 连接的不是同一个Redis,
|
||||
所以Client端校验Ticket时无法在Redis中查询到相应的值,才会产生异常:“Ticket无效”
|
||||
|
||||
要使上述代码生效很简单,我们只需更改一下校验Ticket的逻辑即可,将 `H5Controller` 中的 `checkTicket` 方法代码改为:
|
||||
``` java
|
||||
// 校验 Ticket码,获取账号Id
|
||||
private Object checkTicket(String ticket) {
|
||||
SaSsoConfig cfg = SaManager.getConfig().getSso();
|
||||
String ssoLogoutCall = null;
|
||||
if(cfg.isSlo) {
|
||||
ssoLogoutCall = SaHolder.getRequest().getUrl().replace("/doLoginByTicket", Api.ssoLogoutCall);
|
||||
}
|
||||
String checkUrl = SaSsoUtil.buildCheckTicketUrl(ticket, ssoLogoutCall);
|
||||
Object body = cfg.sendHttp.apply(checkUrl);
|
||||
return (SaFoxUtil.isEmpty(body) ? null : body);
|
||||
}
|
||||
```
|
||||
|
||||
重新运行项目,即可在SSO模式三中成功整合前后台分离模式 。
|
||||
|
||||
|
||||
|
||||
|
52
sa-token-doc/doc/sso/sso-pro.md
Normal file
52
sa-token-doc/doc/sso/sso-pro.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Sa-Sso-Pro 单点登录商业版
|
||||
|
||||
### 项目介绍
|
||||
|
||||
根据 Sa-Token SSO 模块文档,以及官网提供的源码示例,您可以很方便的搭建一个SSO模式的认证Demo。
|
||||
|
||||
<!-- 然而对于一些企业级项目,简单的demo示例,显然无法完成我们的项目需求,要真正开发一个商业级项目的认证中心系统,绝非一朝一夕可以搭建完毕, -->
|
||||
|
||||
然而,要真正开发一个商业级项目的认证中心系统,绝非一朝一夕可以搭建完毕,其必不可少的一些功能,
|
||||
比如:用户账号增删改查维护、登录日志统计、新增用户数据报表、新增 Client 应用接入域名配置……等等,
|
||||
仍需要我们大量的开发时间。
|
||||
|
||||
为此,我们特意准备了项目:[[Sa-Sso-Pro 单点登录商业版]](http://sa-pro.dev33.cn/index.html?hmsr=sa-token),
|
||||
项目集成了单点登录常见技术点, 绝大多数功能无需二次开发,直接可用,<b style="color: #FF5722;">可大大缩短您的项目接入单点登录的开发周期</b>。
|
||||
|
||||
|
||||
### 释疑
|
||||
|
||||
##### 1、Sa-Sso-Pro 是收费项目吗?与 Sa-Token 有什么不同?
|
||||
|
||||
`Sa-Sso-Pro` 是付费项目,暂不开放源码,如需使用需要购买项目授权,您可以在其主页了解更多详细信息。
|
||||
|
||||
`Sa-Sso-Pro` 与 `Sa-Token` 的区别,简单来讲:
|
||||
- `Sa-Token` 是一个框架,需要在项目中通过 pom.xml 引入
|
||||
- `Sa-Sso-Pro` 是一个完整项目,下载源码后可直接启动
|
||||
|
||||
|
||||
##### 2、Sa-Token 会不会在某一天收费?导致我们项目无法正常运行?
|
||||
首先我们需要了解一点:**已经发布到 Maven 中央仓库的代码,是不可以删除的**,所以这部分代码是无法做到收费的
|
||||
|
||||
其次,像中间件框架,业界没有收费的先例,也没有对应的商业模式,一般的付费项目都是一些成型的完整项目,以解决特定场景的业务需求为目的,
|
||||
比如:聊天通信、刷脸认证、短信验证码、聚合支付……等等。
|
||||
|
||||
Sa-Sso-Pro 并非随意收费,只有当您的系统需要 **统一认证中心** 时您才会用到它,花一笔小钱节省大量开发工期,整体来看,这是非常划算的。
|
||||
|
||||
另外:即使您没有购买 `Sa-Sso-Pro`,也不会影响到您对 `Sa-Token` 的使用,举个例子:MySql具有社区版与企业版,即使您没有购买其付费版,也不会影响到您对免费 MySql 的使用。
|
||||
|
||||
|
||||
|
||||
##### 3、Sa-Token 团队日后的主要精力是不是放在 Sa-Sso-Pro 上,降低对 Sa-Token 的支持?毕竟 Sa-Token 是免费的!
|
||||
|
||||
答案是不会。
|
||||
|
||||
再次强调一下:`Sa-Token` 与 `Sa-Sso-Pro` 是两个独立的项目,两者互不影响。
|
||||
付费项目的出现不会降低对 `Sa-Token` 的支持,`Sa-Token`将会按照原有的发展继续升级迭代。
|
||||
|
||||
实际结果可能会恰恰相反:有了盈利来源,`Sa-Token`将发展的更快。
|
||||
|
||||
<!-- 衷心感谢每一位粉丝的支持! -->
|
||||
|
||||
|
||||
|
29
sa-token-doc/doc/sso/sso-questions.md
Normal file
29
sa-token-doc/doc/sso/sso-questions.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Sa-Token-SSO整合-常见问题总结
|
||||
|
||||
---
|
||||
|
||||
### 问:在模式一与模式二中,Client端 必须通过 Alone-Redis 插件来访问Redis吗?
|
||||
答:不必须,只是推荐,权限缓存与业务缓存分离后会减少 `SSO-Redis` 的访问压力,且可以避免多个 `Client端` 的缓存读写冲突
|
||||
|
||||
### 问:将旧有系统改造为单点登录时,应该注意哪些?
|
||||
答:建议不要把其中一个系统改造为SSO服务端,而是新起一个项目作为Server端,所有旧有项目全部作为Client端与此对接
|
||||
|
||||
### 问:SSO模式二,第一个域名登录成功之后其他两个不会自动登录?
|
||||
答:系统1登录成功之后,系统二与系统三需要点击登录按钮,才会登录成功
|
||||
|
||||
> 第一个系统,需要:点击 [登录] 按钮 -> 跳转到登录页 -> 输账号密码 -> 登录成功 <br>
|
||||
> 第二个系统,需要:点击 [登录] 按钮 -> 登录成功 <br>
|
||||
> 第三个系统,需要:点击 [登录] 按钮 -> 登录成功 (免去重复跳转登录页输入账号密码的步骤)
|
||||
|
||||
### 追问:那我是否可以设计成不需要点登录按钮的,只要访问页面,它就能登录成功
|
||||
可以:加个过滤器检测到未登录 自动跳转就行了,详细可以参照章节:[[何时引导用户去登录]](/sso/sso-custom-login) 给出的建议进行设计
|
||||
|
||||
### 问:我参照文档的SSO模式二搭建,一直提示:Ticket无效,请问怎么回事?
|
||||
根据群友的反馈,出现此异常概率最大的原因是因为 `Client` 与 `Server` 没有连接同一个Redis,SSO模式二中两者必须连接同一个 Redis 才可以登录成功,
|
||||
如果您排查之后不是此原因,可以加入QQ群或者在issues反馈一下
|
||||
|
||||
### 还有其它问题?
|
||||
可以加群反馈一下,比较典型的问题我们解决之后都会提交到此页面方便大家快速排查
|
||||
|
||||
|
||||
|
@@ -138,7 +138,7 @@ spring:
|
||||
# Redis服务器连接密码(默认为空)
|
||||
password:
|
||||
```
|
||||
注意点:`allow-url`为了方便测试配置为`*`,线上生产环境一定要配置为详细URL地址 (详见下方“配置域名校验”)
|
||||
注意点:`allow-url`为了方便测试配置为`*`,线上生产环境一定要配置为详细URL地址 (之后的章节我们会详细阐述此配置项)
|
||||
|
||||
##### 2.4、创建SSO-Server端启动类
|
||||
``` java
|
||||
@@ -313,53 +313,12 @@ public class SaSsoClientApplication {
|
||||
默认测试密码:`sa / 123456`,其余流程保持不变
|
||||
|
||||
|
||||
### 6、配置域名校验
|
||||
|
||||
##### 6.1、Ticket劫持攻击
|
||||
在以上的SSO-Server端示例中,配置项 `sa-token.sso.allow-url=*` 意为配置所有允许的Client端授权地址,不在此配置项中的URL将无法单点登录成功
|
||||
|
||||
以上示例为了方便测试被配置为*,但是,<font color="#FF0000" >在生产环境中,此配置项绝对不能配置为 * </font>,否则会有被ticket劫持的风险
|
||||
|
||||
假设攻击者根据模仿我们的授权地址,巧妙的构造一个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/)
|
||||
|
||||
当不知情的小红被诱导访问了这个URL时,它将被重定向至百度首页
|
||||
|
||||

|
||||
|
||||
可以看到,代表着用户身份的ticket码也显现到了URL之中,借此漏洞,攻击者完全可以构建一个URL将小红的ticket码自动提交到攻击者自己的服务器,伪造小红身份登录网站
|
||||
|
||||
##### 6.2、防范方法
|
||||
|
||||
造成此漏洞的直接原因就是SSO-Server认证中心没有对 `redirect地址` 进行任何的限制,防范的方法也很简单,就是对`redirect参数`进行校验,如果其不在指定的URL列表中时,拒绝下放ticket
|
||||
|
||||
我们将其配置为一个具体的URL:`allow-url=http://sa-sso-client1.com:9001/sso/login`,再次访问上述连接:
|
||||
|
||||

|
||||
|
||||
域名没有通过校验,拒绝授权!
|
||||
|
||||
##### 6.3、配置安全性参考表
|
||||
|
||||
| 配置方式 | 举例 | 安全性 | 建议 |
|
||||
| :-------- | :-------- | :-------- | :-------- |
|
||||
| 配置为* | `*` | <font color="#F00" >低</font> | **<font color="#F00" >禁止在生产环境下使用</font>** |
|
||||
| 配置到域名 | `http://sa-sso-client1.com/*` | <font color="#F70" >中</font> | <font color="#F70" >不建议在生产环境下使用</font> |
|
||||
| 配置到详细地址| `http://sa-sso-client1.com:9001/sso/login` | <font color="#080" >高</font> | <font color="#080" >可以在生产环境下使用</font> |
|
||||
|
||||
|
||||
##### 6.4、疑问:为什么不直接回传Token,而是先回传ticket,再用ticket去查询对应的账号id?
|
||||
Token作为长时间有效的会话凭证,在任何时候都不应该直接在暴露URL之中(虽然Token直接的暴露本身不会造成安全漏洞,但会为很多漏洞提供可乘之机)
|
||||
|
||||
因此Sa-Token-SSO选择先回传ticket,再由ticket获取账号id,且ticket一次性用完即废,提高安全性
|
||||
|
||||
|
||||
|
||||
### 7、跨Redis的单点登录
|
||||
### 6、跨Redis的单点登录
|
||||
以上流程解决了跨域模式下的单点登录,但是后端仍然采用了共享Redis来同步会话,如果我们的架构设计中Client端与Server端无法共享Redis,又该怎么完成单点登录?
|
||||
|
||||
这就要采用模式三了,且往下看:[Http请求获取会话](/sso/sso-type3)
|
||||
这就要采用模式三了,且往下看:[SSO模式三:Http请求获取会话](/sso/sso-type3)
|
||||
|
||||
|
||||
<!--
|
||||
|
Reference in New Issue
Block a user