From bea2592dc9cddbe659cfb8e6d60bea66a71f211a Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Fri, 23 Jul 2021 02:13:44 +0800 Subject: [PATCH] =?UTF-8?q?=E6=99=9A=E4=B8=8A=E5=8D=95=E7=82=B9=E7=99=BB?= =?UTF-8?q?=E5=BD=95=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../satoken/context/model/SaRequest.java | 2 +- .../java/com/pj/satoken/MySaTokenAction.java | 18 --- sa-token-doc/doc/_sidebar.md | 1 + sa-token-doc/doc/sso/sso-cd.md | 127 ++++++++++++++++++ sa-token-doc/doc/sso/sso-type2.md | 58 ++++---- 5 files changed, 158 insertions(+), 48 deletions(-) delete mode 100644 sa-token-demo/sa-token-demo-springboot/src/main/java/com/pj/satoken/MySaTokenAction.java create mode 100644 sa-token-doc/doc/sso/sso-cd.md diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/context/model/SaRequest.java b/sa-token-core/src/main/java/cn/dev33/satoken/context/model/SaRequest.java index 66ac2ec0..5d88dd4c 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/context/model/SaRequest.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/context/model/SaRequest.java @@ -92,7 +92,7 @@ public interface SaRequest { } /** - * 返回当前请求的url,例:http://xxx.com/?id=127 + * 返回当前请求的url,例:http://xxx.com/ * @return see note */ public String getUrl(); diff --git a/sa-token-demo/sa-token-demo-springboot/src/main/java/com/pj/satoken/MySaTokenAction.java b/sa-token-demo/sa-token-demo-springboot/src/main/java/com/pj/satoken/MySaTokenAction.java deleted file mode 100644 index bd7d5790..00000000 --- a/sa-token-demo/sa-token-demo-springboot/src/main/java/com/pj/satoken/MySaTokenAction.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.pj.satoken; - -import org.springframework.stereotype.Component; - -import cn.dev33.satoken.action.SaTokenActionDefaultImpl; -import cn.dev33.satoken.util.SaFoxUtil; - -/** - * 继承Sa-Token行为Bean默认实现, 重写部分逻辑 - */ -@Component -public class MySaTokenAction extends SaTokenActionDefaultImpl { - // 重写token生成策略 - @Override - public String createToken(Object loginId, String loginType) { - return SaFoxUtil.getRandomString(60); // 随机60位字符串 - } -} diff --git a/sa-token-doc/doc/_sidebar.md b/sa-token-doc/doc/_sidebar.md index ce51a69b..edc77037 100644 --- a/sa-token-doc/doc/_sidebar.md +++ b/sa-token-doc/doc/_sidebar.md @@ -36,6 +36,7 @@ - [SSO模式一 共享Cookie同步会话](/sso/sso-type1) - [SSO模式二 URL重定向传播会话](/sso/sso-type2) - [SSO模式三 Http请求获取会话](/sso/sso-type3) + - [SSO整合-常见问题总结](/sso/sso-cd) - **OAuth2.0** - [OAuth2.0简述](/oauth2/readme) diff --git a/sa-token-doc/doc/sso/sso-cd.md b/sa-token-doc/doc/sso/sso-cd.md new file mode 100644 index 00000000..dc6cc763 --- /dev/null +++ b/sa-token-doc/doc/sso/sso-cd.md @@ -0,0 +1,127 @@ +# Sa-Token-SSO整合-常见问题总结 + +--- + + +### 一、何时引导用户去登录? + +以下解决方案三选一: + +##### 1.1、前端按钮跳转 +前端页面准备一个**`[登录]`**按钮,当用户点击按钮时,跳转到登录接口 +``` js +登录 +``` + +##### 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 接口重定向 + + + + +### 二、定制化开发 + +##### 2.1、如何自定义登录视图? +- 方式一:在demo示例中直接更改页面代码 +- 方式二:在配置中配置登录视图地址 + +``` java +cfg.sso +// 配置:未登录时返回的View +.setNotLoginView(() -> { + return new ModelAndView("xxx.html"); +}) +``` + +##### 2.2、如何自定义登录API的接口? +根据需求点选择解决方案: + +**2.2.1、如果只是想在 setDoLoginHandle 函数里获取除 name、pwd 以外的参数?** +``` java +// 在任意代码处获取前端提交的参数 +String xxx = SaHolder.getRequest().getParam("xxx"); +``` + +**2.2.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("登录失败!"); +} +``` + +**2.2.3、不想使用`/sso/doLogin`这个接口,想自定义一个API地址?** + +答:直接在前端更改点击按钮时 Ajax 的请求地址即可 + + +### 三、常见疑问 + +##### 问:在模式一与模式二中,Client端 必须通过 Alone-Redis 插件来访问Redis吗? + +答:不必须,只是推荐,权限缓存与业务缓存分离后会减少SSO-Redis的访问压力,且可以避免多个Client端的缓存读写冲突 + + diff --git a/sa-token-doc/doc/sso/sso-type2.md b/sa-token-doc/doc/sso/sso-type2.md index 5b1dd70e..eaa6eae9 100644 --- a/sa-token-doc/doc/sso/sso-type2.md +++ b/sa-token-doc/doc/sso/sso-type2.md @@ -27,12 +27,21 @@ 下面我们按照步骤依次完成上述过程 +### 1、准备工作 +首先修改hosts文件`(C:\windows\system32\drivers\etc\hosts)`,添加以下IP映射,方便我们进行测试: +``` url +127.0.0.1 sa-sso-server.com +127.0.0.1 sa-sso-client1.com +127.0.0.1 sa-sso-client2.com +127.0.0.1 sa-sso-client3.com +``` -### 1、搭建SSO-Server认证中心 + +### 2、搭建SSO-Server认证中心 > 搭建示例在官方仓库的 `/sa-token-demo/sa-token-demo-sso2-server/`,如遇到难点可结合源码进行测试学习 -##### 1.1、创建SSO-Server端项目 +##### 2.1、创建SSO-Server端项目 创建SpringBoot项目 `sa-token-demo-sso-server`(不会的同学自行百度或参考仓库示例),添加pom依赖: ``` xml @@ -55,7 +64,7 @@ ``` -##### 1.2、创建SSO-Server端认证接口 +##### 2.2、创建SSO-Server端认证接口 ``` java /** * Sa-Token-SSO Server端 Controller @@ -96,7 +105,7 @@ public class SsoServerController { ``` 注意:在`setDoLoginHandle`函数里如果要获取name, pwd以外的参数,可通过`SaHolder.getRequest().getParam("xxx")`来获取 -##### 1.4、application.yml配置 +##### 2.3、application.yml配置 ``` yml # 端口 server: @@ -123,9 +132,9 @@ spring: # Redis服务器连接密码(默认为空) password: ``` -注意点:`allow-url`为了方便测试配置为*,线上生产环境一定要配置为详细URL地址 (详见下方“配置域名校验”) +注意点:`allow-url`为了方便测试配置为`*`,线上生产环境一定要配置为详细URL地址 (详见下方“配置域名校验”) -##### 1.4、创建SSO-Server端启动类 +##### 2.4、创建SSO-Server端启动类 ``` java @SpringBootApplication public class SaSsoServerApplication { @@ -137,11 +146,11 @@ public class SaSsoServerApplication { ``` -### 2、搭建SSO-Client应用端 +### 3、搭建SSO-Client应用端 > 搭建示例在官方仓库的 `/sa-token-demo/sa-token-demo-sso2-client/`,如遇到难点可结合源码进行测试学习 -##### 2.1、创建SSO-Client端项目 +##### 3.1、创建SSO-Client端项目 创建一个SpringBoot项目 `sa-token-demo-sso-client`,添加pom依赖: ``` xml @@ -171,7 +180,7 @@ public class SaSsoServerApplication { ``` -##### 1.2、创建SSO-Client端认证接口 +##### 3.2、创建SSO-Client端认证接口 ``` java /** @@ -199,7 +208,7 @@ public class SsoClientController { } ``` -##### 1.3、配置SSO认证中心地址 +##### 3.3、配置SSO认证中心地址 你需要在 `application.yml` 配置如下信息: ``` yml # 端口 @@ -228,7 +237,7 @@ sa-token: ``` 注意点:`sa-token.alone-redis` 的配置需要和SSO-Server端连接同一个Redis(database也要一样) -##### 1.4、写启动类 +##### 3.4、写启动类 ``` java @SpringBootApplication public class SaSsoClientApplication { @@ -241,18 +250,8 @@ public class SaSsoClientApplication { 启动项目 -### 3、测试访问 +### 4、测试访问 -##### 3.1 修改host文件 -首先修改hosts文件`(C:\windows\system32\drivers\etc\hosts)`,添加以下IP映射,方便我们进行测试: -``` url -127.0.0.1 sa-sso-server.com -127.0.0.1 sa-sso-client1.com -127.0.0.1 sa-sso-client2.com -127.0.0.1 sa-sso-client3.com -``` - -##### 3.2 启动项目并访问 (1) 依次启动SSO-Server与SSO-Client端,然后从浏览器访问:[http://sa-sso-client1.com:9001/](http://sa-sso-client1.com:9001/) ![sso-client-index.png](https://oss.dev33.cn/sa-token/doc/sso/sso-client-index.png 's-w-sh') @@ -292,7 +291,7 @@ public class SaSsoClientApplication { ![sso-genzong](https://oss.dev33.cn/sa-token/doc/sso/sso-genzong.png 's-w-sh') -### 4、运行官方仓库 +### 5、运行官方仓库 以上示例,虽然完整的复现了单点登录的过程,但是页面还是有些简陋,我们可以运行一下官方仓库的示例,里面有制作好的登录页面 @@ -303,9 +302,9 @@ public class SaSsoClientApplication { 默认测试密码:`sa / 123456`,其余流程保持不变 -### 5、配置域名校验 +### 6、配置域名校验 -##### 5.1、Ticket劫持攻击 +##### 6.1、Ticket劫持攻击 在以上的SSO-Server端示例中,配置项 `sa-token.sso.allow-url=*` 意为配置所有允许的Client端授权地址,不在此配置项中的URL将无法单点登录成功 以上示例为了方便测试被配置为*,但是,在生产环境中,此配置项绝对不能配置为 * ,否则会有被ticket劫持的风险 @@ -320,7 +319,7 @@ public class SaSsoClientApplication { 可以看到,代表着用户身份的ticket码也显现到了URL之中,借此漏洞,攻击者完全可以构建一个URL将小红的ticket码自动提交到攻击者自己的服务器,伪造小红身份登录网站 -##### 5.2、防范方法 +##### 6.2、防范方法 造成此漏洞的直接原因就是SSO-Server认证中心没有对 `redirect地址` 进行任何的限制,防范的方法也很简单,就是对`redirect参数`进行校验,如果其不在指定的URL列表中时,拒绝下放ticket @@ -330,7 +329,7 @@ public class SaSsoClientApplication { 域名没有通过校验,拒绝授权! -##### 5.3、配置安全性参考表 +##### 6.3、配置安全性参考表 | 配置方式 | 举例 | 安全性 | 建议 | | :-------- | :-------- | :-------- | :-------- | @@ -339,13 +338,14 @@ public class SaSsoClientApplication { | 配置到详细地址| `http://sa-sso-client1.com:9001/sso/login` | | 可以在生产环境下使用 | -##### 5.4、疑问:为什么不直接回传Token,而是先回传ticket,再用ticket去查询对应的账号id? +##### 6.4、疑问:为什么不直接回传Token,而是先回传ticket,再用ticket去查询对应的账号id? Token作为长时间有效的会话凭证,在任何时候都不应该直接在暴露URL之中(虽然Token直接的暴露本身不会造成安全漏洞,但会为很多漏洞提供可乘之机) 因此Sa-Token-SSO选择先回传ticket,再由ticket获取账号id,且ticket一次性用完即废,提高安全性 -### 6、跨Redis的单点登录 + +### 7、跨Redis的单点登录 以上流程解决了跨域模式下的单点登录,但是后端仍然采用了共享Redis来同步会话,如果我们的架构设计中Client端与Server端无法共享Redis,又该怎么完成单点登录? 这就要采用模式三了,且往下看:[Http请求获取会话](/sso/sso-type3)