diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/java/com/pj/oauth2/SaOAuthClientController.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/java/com/pj/oauth2/SaOAuthClientController.java
index 77240d0a..ffa9fe0c 100644
--- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/java/com/pj/oauth2/SaOAuthClientController.java
+++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/java/com/pj/oauth2/SaOAuthClientController.java
@@ -23,7 +23,7 @@ public class SaOAuthClientController {
// 相关参数配置
private final String clientId = "1001"; // 应用id
private final String clientSecret = "aaaa-bbbb-cccc-dddd-eeee"; // 应用秘钥
- private final String serverUrl = "http://sa-oauth-server.com:8001"; // 服务端接口
+ private final String serverUrl = "http://sa-oauth-server.com:8000"; // 服务端接口
// 进入首页
@RequestMapping("/")
diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/resources/templates/index.html b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/resources/templates/index.html
index 711f1a97..79c33db3 100644
--- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/resources/templates/index.html
+++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-client/src/main/resources/templates/index.html
@@ -42,33 +42,33 @@
模式一:授权码(Authorization Code)
授权码:OAuth2.0标准授权流程,先 (重定向) 获取Code授权码,再 (Rest API) 获取 Access-Token 和 Openid
-
+
当请求链接不包含 scope 权限,或请求的 scope 近期已授权时,将无需用户手动确认,做到静默授权
- http://sa-oauth-server.com:8001/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/
+ http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/
-
+
当请求链接包含具体的 scope 权限时,将需要用户手动确认,此时 OAuth-Server 会返回更多的数据
- http://sa-oauth-server.com:8001/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=openid,userid,userinfo
+ http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=openid,userid,userinfo
我们可以拿着 Refresh-Token 去刷新我们的 Access-Token,每次刷新后旧Token将作废
- http://sa-oauth-server.com:8001/oauth2/refresh?grant_type=refresh_token&client_id={value}&client_secret={value}&refresh_token={value}
+ http://sa-oauth-server.com:8000/oauth2/refresh?grant_type=refresh_token&client_id={value}&client_secret={value}&refresh_token={value}
使用 Access-Token 置换资源: 获取账号昵称、头像、性别等信息 (Access-Token具备userinfo权限时才可以获取成功)
- http://sa-oauth-server.com:8001/oauth2/userinfo?access_token={value}
+ http://sa-oauth-server.com:8000/oauth2/userinfo?access_token={value}
模式二:隐藏式(Implicit)
-
+
越过授权码的步骤,直接返回token到前端页面( 格式:http//:domain.com#token=xxxx-xxxx )
- http://sa-oauth-server.com:8001/oauth2/authorize?response_type=token&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=userinfo
+ http://sa-oauth-server.com:8000/oauth2/authorize?response_type=token&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=userinfo
模式三:密码式(Password)
@@ -76,7 +76,7 @@
账号:
密码:
- http://sa-oauth-server.com:8001/oauth2/token?grant_type=password&client_id={value}&client_secret={value}&username={value}&password={value}
+ http://sa-oauth-server.com:8000/oauth2/token?grant_type=password&client_id={value}&client_secret={value}&username={value}&password={value}
模式四:凭证式(Client Credentials)
@@ -87,7 +87,7 @@
保证了服务的高可用
- http://sa-oauth-server.com:8001/oauth2/client_token?grant_type=client_credentials&client_id={value}&client_secret={value}
+ http://sa-oauth-server.com:8000/oauth2/client_token?grant_type=client_credentials&client_id={value}&client_secret={value}
更多资料请参考 Sa-Token 官方文档地址:
diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/current/GlobalExceptionHandler.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/current/GlobalExceptionHandler.java
new file mode 100644
index 00000000..f251e478
--- /dev/null
+++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/current/GlobalExceptionHandler.java
@@ -0,0 +1,22 @@
+package com.pj.current;
+
+import cn.dev33.satoken.util.SaResult;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+/**
+ * 全局异常处理
+ * @author click33
+ *
+ */
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+
+ // 全局异常拦截
+ @ExceptionHandler
+ public SaResult handlerException(Exception e) {
+ e.printStackTrace();
+ return SaResult.error(e.getMessage());
+ }
+
+}
diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2DataLoaderImpl.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2DataLoaderImpl.java
index e8358a24..72948364 100644
--- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2DataLoaderImpl.java
+++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2DataLoaderImpl.java
@@ -12,22 +12,22 @@ import org.springframework.stereotype.Component;
@Component
public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader {
- // 根据 client_id 获取 Client 信息
+ // 根据 clientId 获取 Client 信息
@Override
public SaClientModel getClientModel(String clientId) {
// 此为模拟数据,真实环境需要从数据库查询
if("1001".equals(clientId)) {
return new SaClientModel()
- .setClientId("1001")
- .setClientSecret("aaaa-bbbb-cccc-dddd-eeee")
- .addAllowUrls("*")
- .addContractScopes("openid", "userid", "userinfo")
- .setIsAutoMode(true);
+ .setClientId("1001") // client id
+ .setClientSecret("aaaa-bbbb-cccc-dddd-eeee") // client 秘钥
+ .addAllowUrls("*") // 所有允许授权的 url
+ .addContractScopes("openid", "userid", "userinfo") // 所有签约的权限
+ .setIsAutoMode(true); // 是否自动判断开放的授权模式
}
return null;
}
- // 根据ClientId 和 LoginId 获取openid
+ // 根据 clientId 和 loginId 获取 openid
@Override
public String getOpenid(String clientId, Object loginId) {
// 此处使用框架默认算法生成 openid,真实环境建议改为从数据库查询
diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java
index 03fbbf60..44ddce9d 100644
--- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java
+++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java
@@ -33,12 +33,13 @@ public class SaOAuth2ServerController {
// Sa-OAuth2 定制化配置
@Autowired
- public void setSaOAuth2Config(SaOAuth2Config cfg) {
+ public void configOAuth2Server(SaOAuth2Config cfg) {
// 未登录的视图
cfg.notLoginView = ()->{
return new ModelAndView("login.html");
};
- // 登录处理函数
+
+ // 登录处理函数
cfg.doLoginHandle = (name, pwd) -> {
if("sa".equals(name) && "123456".equals(pwd)) {
StpUtil.login(10001);
@@ -46,11 +47,12 @@ public class SaOAuth2ServerController {
}
return SaResult.error("账号名或密码错误");
};
- // 授权确认视图
- cfg.confirmView = (clientId, scope)->{
+
+ // 授权确认视图
+ cfg.confirmView = (clientId, scopes)->{
Map map = new HashMap<>();
map.put("clientId", clientId);
- map.put("scope", scope);
+ map.put("scope", scopes);
return new ModelAndView("confirm.html", map);
};
}
diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml
index f745a8d9..0d263504 100644
--- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml
+++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/resources/application.yml
@@ -1,5 +1,5 @@
server:
- port: 8001
+ port: 8000
# sa-token配置
sa-token:
diff --git a/sa-token-doc/_sidebar.md b/sa-token-doc/_sidebar.md
index e07e2cc9..e15f364c 100644
--- a/sa-token-doc/_sidebar.md
+++ b/sa-token-doc/_sidebar.md
@@ -37,7 +37,7 @@
- **单点登录**
- [单点登录简述](/sso/readme)
- [搭建统一认证中心:SSO-Server](/sso/sso-server)
- - [SSO-Server 认证中心开放接口](/sso/sso-apidoc)
+ - [SSO-Server 认证中心开放 API 接口](/sso/sso-apidoc)
- [SSO模式一 共享Cookie同步会话](/sso/sso-type1)
- [SSO模式二 URL重定向传播会话](/sso/sso-type2)
- [SSO模式三 Http请求获取会话](/sso/sso-type3)
@@ -54,9 +54,18 @@
- **OAuth2.0**
- [OAuth2.0简述](/oauth2/readme)
- [OAuth2-Server搭建](/oauth2/oauth2-server)
- - [OAuth2-Server端-API列表](/oauth2/oauth2-api)
- - [OAuth2-二次开发说明](/oauth2/oauth2-dev)
+ - [OAuth2-Server端开放 API 接口](/oauth2/oauth2-apidoc)
+ - [配置 client 域名校验 ](/oauth2/oauth2-check-domain)
+ - [定制化登录页面与授权页面](/oauth2/oauth2-custom-login)
+ - [拆分式路由](/oauth2/3)
+ - [前后端分离模式整合方案](/oauth2/4)
+ - [平台中心模式开发](/oauth2/5)
+ - [自定义 Scope 权限以处理器](/oauth2/6)
+ - [为 Scope 划分等级](/oauth2/7)
- [OAuth2-与登录会话实现数据互通](/oauth2/oauth2-interworking)
+ - [OAuth2 代码 API 参考](/oauth2/oauth2-dev)
+ - [常见问题说明](/oauth2/8)
+ -
- **微服务**
- [分布式Session会话](/micro/dcs-session)
diff --git a/sa-token-doc/fun/sso-vs-oauth2.md b/sa-token-doc/fun/sso-vs-oauth2.md
index a3cd5764..e8dd8b0f 100644
--- a/sa-token-doc/fun/sso-vs-oauth2.md
+++ b/sa-token-doc/fun/sso-vs-oauth2.md
@@ -14,6 +14,7 @@ QQ群库经常有小伙伴提问:项目需要搭建统一认证中心,是用
| 自有系统授权管理 | 支持度高 | 支持度低 |
| Client级的权限校验 | 不支持 | 支持度高 |
| 集成简易度 | 比较简单 | 难度中等 |
+| 适合项目 | 企业内部项目整合 | 企业搭建统一认证授权平台,对外开放服务 |
注:以上仅为在 Sa-Token 中两种技术的差异度比较,不同框架的实现可能略有差异,但整体思想是一致的。
diff --git a/sa-token-doc/oauth2/oauth2-api.md b/sa-token-doc/oauth2/oauth2-api.md
deleted file mode 100644
index a214c271..00000000
--- a/sa-token-doc/oauth2/oauth2-api.md
+++ /dev/null
@@ -1,265 +0,0 @@
-# Sa-Token-OAuth2 Server端 API列表
-基于官方仓库的搭建示例,`OAuth2-Server`端会暴露出以下API,`OAuth2-Client`端可据此文档进行对接
-
----
-
-## 1、模式一:授权码(Authorization Code)
-
-### 1.1、获取授权码
-
-根据以下格式构建URL,引导用户访问 (复制时请注意删减掉相应空格和换行符)
-``` url
-http://sa-oauth-server.com:8001/oauth2/authorize
- ?response_type=code
- &client_id={value}
- &redirect_uri={value}
- &scope={value}
- &state={value}
-```
-
-参数详解:
-
-| 参数 | 是否必填 | 说明 |
-| :-------- | :-------- | :-------- |
-| response_type | 是 | 返回类型,这里请填写:code |
-| client_id | 是 | 应用id |
-| redirect_uri | 是 | 用户确认授权后,重定向的url地址 |
-| scope | 否 | 具体请求的权限,多个用逗号隔开 |
-| state | 否 | 随机值,此参数会在重定向时追加到url末尾,不填不追加 |
-
-注意点:
-1. 如果用户在Server端尚未登录:会被转发到登录视图,你可以参照文档或官方示例自定义登录页面
-2. 如果scope参数为空,或者请求的权限用户近期已确认过,则无需用户再次确认,达到静默授权的效果,否则需要用户手动确认,服务器才可以下放code授权码
-
-用户确认授权之后,会被重定向至`redirect_uri`,并追加code参数与state参数,形如:
-``` url
-redirect_uri?code={code}&state={state}
-```
-
-Code授权码具有以下特点:
-1. 每次授权产生的Code码都不一样
-2. Code码用完即废,不能二次使用
-3. 一个Code的有效期默认为五分钟,超时自动作废
-4. 每次授权产生新Code码,会导致旧Code码立即作废,即使旧Code码尚未使用
-
-
-### 1.2、根据授权码获取Access-Token
-获得Code码后,我们可以通过以下接口,获取到用户的`Access-Token`、`Refresh-Token`、`openid`等关键信息
-
-``` url
-http://sa-oauth-server.com:8001/oauth2/token
- ?grant_type=authorization_code
- &client_id={value}
- &client_secret={value}
- &code={value}
-```
-
-参数详解:
-
-| 参数 | 是否必填 | 说明 |
-| :-------- | :-------- | :-------- |
-| grant_type | 是 | 授权类型,这里请填写:authorization_code |
-| client_id | 是 | 应用id |
-| client_secret | 是 | 应用秘钥 |
-| code | 是 | 步骤1.1中获取到的授权码 |
-
-接口返回示例:
-
-``` js
-{
- "code": 200, // 200表示请求成功,非200标识请求失败, 以下不再赘述
- "msg": "ok",
- "data": {
- "access_token": "7Ngo1Igg6rieWwAmWMe4cxT7j8o46mjyuabuwLETuAoN6JpPzPO2i3PVpEVJ", // Access-Token值
- "refresh_token": "ZMG7QbuCVtCIn1FAJuDbgEjsoXt5Kqzii9zsPeyahAmoir893ARA4rbmeR66", // Refresh-Token值
- "expires_in": 7199, // Access-Token剩余有效期,单位秒
- "refresh_expires_in": 2591999, // Refresh-Token剩余有效期,单位秒
- "client_id": "1001", // 应用id
- "scope": "userinfo", // 此令牌包含的权限
- "openid": "gr_SwoIN0MC1ewxHX_vfCW3BothWDZMMtx__" // openid
- }
-}
-```
-
-
-### 1.3、根据 Refresh-Token 刷新 Access-Token (如果需要的话)
-Access-Token的有效期较短,如果每次过期都需要重新授权的话,会比较影响用户体验,因此我们可以在后台通过`Refresh-Token` 刷新 `Access-Token`
-
-``` url
-http://sa-oauth-server.com:8001/oauth2/refresh
- ?grant_type=refresh_token
- &client_id={value}
- &client_secret={value}
- &refresh_token={value}
-```
-
-参数详解:
-
-| 参数 | 是否必填 | 说明 |
-| :-------- | :-------- | :-------- |
-| grant_type | 是 | 授权类型,这里请填写:refresh_token |
-| client_id | 是 | 应用id |
-| client_secret | 是 | 应用秘钥 |
-| refresh_token | 是 | 步骤1.2中获取到的`Refresh-Token`值 |
-
-接口返回值同章节1.2,此处不再赘述
-
-
-### 1.4、回收 Access-Token (如果需要的话)
-在Access-Token过期前主动将其回收
-
-``` url
-http://sa-oauth-server.com:8001/oauth2/revoke
- ?client_id={value}
- &client_secret={value}
- &access_token={value}
-```
-
-参数详解:
-
-| 参数 | 是否必填 | 说明 |
-| :-------- | :-------- | :-------- |
-| client_id | 是 | 应用id |
-| client_secret | 是 | 应用秘钥 |
-| access_token | 是 | 步骤1.2中获取到的`Access-Token`值 |
-
-返回值样例:
-``` js
-{
- "code": 200,
- "msg": "ok",
- "data": null
-}
-```
-
-
-### 1.5、根据 Access-Token 获取相应用户的账号信息
-注:此接口为官方仓库模拟接口,正式项目中大家可以根据此样例,自定义需要的接口及参数
-
-``` url
-http://sa-oauth-server.com:8001/oauth2/userinfo?access_token={value}
-```
-
-返回值样例:
-``` js
-{
- "code": 200,
- "msg": "ok",
- "data": {
- "nickname": "shengzhang_", // 账号昵称
- "avatar": "http://xxx.com/1.jpg", // 头像地址
- "age": "18", // 年龄
- "sex": "男", // 性别
- "address": "山东省 青岛市 城阳区" // 所在城市
- }
-}
-```
-
-
-## 2、模式二:隐藏式(Implicit)
-
-根据以下格式构建URL,引导用户访问:
-``` url
-http://sa-oauth-server.com:8001/oauth2/authorize
- ?response_type=token
- &client_id={value}
- &redirect_uri={value}
- &scope={value}
- $state={value}
-```
-
-参数详解:
-
-| 参数 | 是否必填 | 说明 |
-| :-------- | :-------- | :-------- |
-| response_type | 是 | 返回类型,这里请填写:token |
-| client_id | 是 | 应用id |
-| redirect_uri | 是 | 用户确认授权后,重定向的url地址 |
-| scope | 否 | 具体请求的权限,多个用逗号隔开 |
-| state | 否 | 随机值,此参数会在重定向时追加到url末尾,不填不追加 |
-
-此模式会越过授权码的步骤,直接返回Access-Token到前端页面,形如:
-``` url
-redirect_uri#token=xxxx-xxxx-xxxx-xxxx
-```
-
-## 3、模式三:密码式(Password)
-首先在Client端构建表单,让用户输入Server端的账号和密码,然后在Client端访问接口
-``` url
-http://sa-oauth-server.com:8001/oauth2/token
- ?grant_type=password
- &client_id={value}
- &client_secret={value}
- &username={value}
- &password={value}
-```
-
-参数详解:
-
-| 参数 | 是否必填 | 说明 |
-| :-------- | :-------- | :-------- |
-| grant_type | 是 | 返回类型,这里请填写:password|
-| client_id | 是 | 应用id |
-| client_secret | 是 | 应用秘钥 |
-| username | 是 | 用户的Server端账号 |
-| password | 是 | 用户的Server端密码 |
-| scope | 否 | 具体请求的权限,多个用逗号隔开 |
-
-接口返回示例:
-
-``` js
-{
- "code": 200, // 200表示请求成功,非200标识请求失败, 以下不再赘述
- "msg": "ok",
- "data": {
- "access_token": "7Ngo1Igg6rieWwAmWMe4cxT7j8o46mjyuabuwLETuAoN6JpPzPO2i3PVpEVJ", // Access-Token值
- "refresh_token": "ZMG7QbuCVtCIn1FAJuDbgEjsoXt5Kqzii9zsPeyahAmoir893ARA4rbmeR66", // Refresh-Token值
- "expires_in": 7199, // Access-Token剩余有效期,单位秒
- "refresh_expires_in": 2591999, // Refresh-Token剩余有效期,单位秒
- "client_id": "1001", // 应用id
- "scope": "", // 此令牌包含的权限
- "openid": "gr_SwoIN0MC1ewxHX_vfCW3BothWDZMMtx__" // openid
- }
-}
-```
-
-
-## 4、模式四:凭证式(Client Credentials)
-以上三种模式获取的都是用户的 `Access-Token`,代表用户对第三方应用的授权,
-在OAuth2.0中还有一种针对 Client级别的授权, 即:`Client-Token`,代表应用自身的资源授权
-
-在Client端的后台访问以下接口:
-
-``` url
-http://sa-oauth-server.com:8001/oauth2/client_token
- ?grant_type=client_credentials
- &client_id={value}
- &client_secret={value}
-```
-
-参数详解:
-
-| 参数 | 是否必填 | 说明 |
-| :-------- | :-------- | :-------- |
-| grant_type | 是 | 返回类型,这里请填写:client_credentials|
-| client_id | 是 | 应用id |
-| client_secret | 是 | 应用秘钥 |
-| scope | 否 | 申请权限 |
-
-接口返回值样例:
-``` js
-{
- "code": 200,
- "msg": "ok",
- "data": {
- "client_token": "HmzPtaNuIqGrOdudWLzKJRSfPadN497qEJtanYwE7ZvHQWDy0jeoZJuDIiqO", // Client-Token 值
- "expires_in": 7199, // Token剩余有效时间,单位秒
- "client_id": "1001", // 应用id
- "scope": null // 包含权限
- }
-}
-```
-
-注:`Client-Token`具有延迟作废特性,即:在每次获取最新`Client-Token`的时候,旧`Client-Token`不会立即过期,而是作为`Past-Token`再次储存起来,
-资源请求方只要携带其中之一便可通过Token校验,这种特性保证了在大量并发请求时不会出现“新旧Token交替造成的授权失效”, 保证了服务的高可用
-
diff --git a/sa-token-doc/oauth2/oauth2-apidoc.md b/sa-token-doc/oauth2/oauth2-apidoc.md
new file mode 100644
index 00000000..eedcdf04
--- /dev/null
+++ b/sa-token-doc/oauth2/oauth2-apidoc.md
@@ -0,0 +1,266 @@
+# Sa-Token-OAuth2 Server端 API列表
+基于官方仓库的搭建示例,`OAuth2-Server`端会暴露出以下API,`OAuth2-Client`端可据此文档进行对接
+
+---
+
+## 1、模式一:授权码(Authorization Code)
+
+### 1.1、获取授权码
+
+根据以下格式构建URL,引导用户访问 (复制时请注意删减掉相应空格和换行符)
+``` url
+http://{host}:{port}/oauth2/authorize
+ ?response_type=code
+ &client_id={client_id}
+ &redirect_uri={redirect_uri}
+ &scope={scope}
+ &state={state}
+```
+
+参数详解:
+
+| 参数 | 是否必填 | 说明 |
+| :-------- | :-------- | :-------- |
+| response_type | 是 | 返回类型,这里请填写:`code` |
+| client_id | 是 | 应用 id |
+| redirect_uri | 是 | 用户确认授权后,重定向的 url 地址 |
+| scope | 否 | 具体请求的权限,多个用逗号(或空格)隔开 |
+| state | 否 | 随机值,此参数会在重定向时追加到url末尾,不填不追加,如果填写则每次填写的值不可以重复 |
+
+注意点:
+1. 如果用户在 `OAuth-Server` 端尚未登录:会被转发到登录视图,你可以参照文档或官方示例自定义登录页面。
+2. 如果 `scope` 参数为空,或者请求的 `scope` 用户近期已确认授权过,则无需用户再次确认,达到静默授权的效果,否则需要用户手动确认,服务器才可以下放 `code` 授权码。
+
+用户确认授权之后,会被重定向至`redirect_uri`,并追加 `code` 参数与 `state` 参数,形如:
+``` url
+redirect_uri?code={code}&state={state}
+```
+
+`Code` 授权码具有以下特点:
+1. 每次授权产生的 `Code` 码都不一样。
+2. `Code` 码用完即废,不能二次使用。
+3. 一个 `Code` 的有效期默认为五分钟,超时自动作废。
+4. 每次授权产生新 `Code` 码,会导致旧 `Code` 码立即作废,即使旧 `Code` 码尚未使用。
+
+
+### 1.2、根据授权码获取 Access-Token
+获得 `Code` 码后,我们可以通过以下接口,获取到用户的 `Access-Token`、`Refresh-Token` 等信息。
+
+``` url
+http://{host}:{port}/oauth2/token
+ ?grant_type=authorization_code
+ &client_id={client_id}
+ &client_secret={client_secret}
+ &code={code}
+```
+
+参数详解:
+
+| 参数 | 是否必填 | 说明 |
+| :-------- | :-------- | :-------- |
+| grant_type | 是 | 授权类型,这里请填写:`authorization_code` |
+| client_id | 是 | 应用 id |
+| client_secret | 是 | 应用秘钥 |
+| code | 是 | 步骤 1.1 中获取到的授权码 |
+
+也可以通过 `Basic Authorization` 方式提交 `client` 信息,格式为在请求 `header` 头添加 `Authorization` 参数:
+``` js
+header['Authorization'] = base64(`${client_id}:${client_secret}`);
+```
+
+接口返回示例:
+
+``` js
+{
+ "code": 200, // 200表示请求成功,非200标识请求失败, 以下不再赘述
+ "msg": "ok",
+ "data": null,
+ "token_type": "bearer",
+ "access_token": "Gly7mnnXSdCxkOqmOwcA5SbG6ZtPmJVX7ZgSn1pidhRmnenBEgxbWJS8VWxA", // Access-Token值
+ "refresh_token": "EuYNwpxdc18MpaZLPyhFeyAyzr2IOWEr4q3QUGgPWqdJujQqvohjQEDJpwOm", // Refresh-Token值
+ "expires_in": 7199, // Access-Token剩余有效期,单位秒
+ "refresh_expires_in": 2591999, // Refresh-Token剩余有效期,单位秒
+ "client_id": "1001", // 应用 id
+ "scope": "userinfo" // 此令牌包含的权限
+}
+```
+
+
+### 1.3、根据 Refresh-Token 刷新 Access-Token (如果需要的话)
+Access-Token的有效期较短,如果每次过期都需要重新授权的话,会比较影响用户体验,因此我们可以在后台通过`Refresh-Token` 刷新 `Access-Token`
+
+``` url
+http://{host}:{port}/oauth2/refresh
+ ?grant_type=refresh_token
+ &client_id={client_id}
+ &client_secret={client_secret}
+ &refresh_token={refresh_token}
+```
+
+参数详解:
+
+| 参数 | 是否必填 | 说明 |
+| :-------- | :-------- | :-------- |
+| grant_type | 是 | 授权类型,这里请填写:`refresh_token` |
+| client_id | 是 | 应用 id |
+| client_secret | 是 | 应用秘钥 |
+| refresh_token | 是 | 步骤1.2中获取到的 `Refresh-Token` 值 |
+
+接口返回值同章节1.2,此处不再赘述
+
+
+### 1.4、回收 Access-Token (如果需要的话)
+在A ccess-Token 过期之前主动将其回收
+
+``` url
+http://{host}:{port}/oauth2/revoke
+ ?client_id={client_id}
+ &client_secret={client_secret}
+ &access_token={access_token}
+```
+
+参数详解:
+
+| 参数 | 是否必填 | 说明 |
+| :-------- | :-------- | :-------- |
+| client_id | 是 | 应用 id |
+| client_secret | 是 | 应用秘钥 |
+| access_token | 是 | 步骤1.2中获取到的`Access-Token`值 |
+
+返回值样例:
+``` js
+{
+ "code": 200,
+ "msg": "ok",
+ "data": null
+}
+```
+
+
+### 1.5、根据 Access-Token 获取相应用户的账号信息
+注:此接口为官方仓库模拟接口,正式项目中大家可以根据此样例,自定义需要的接口及参数
+
+``` url
+http://{host}:{port}/oauth2/userinfo?access_token={access_token}
+```
+
+返回值样例:
+``` js
+{
+ "code": 200,
+ "msg": "ok",
+ "nickname": "shengzhang_", // 账号昵称
+ "avatar": "http://xxx.com/1.jpg", // 头像地址
+ "age": "18", // 年龄
+ "sex": "男", // 性别
+ "address": "山东省 青岛市 城阳区" // 所在城市
+}
+```
+
+
+## 2、模式二:隐藏式(Implicit)
+
+根据以下格式构建URL,引导用户访问:
+``` url
+http://{host}:{port}/oauth2/authorize
+ ?response_type=token
+ &client_id={client_id}
+ &redirect_uri={redirect_uri}
+ &scope={scope}
+ &state={state}
+```
+
+参数详解:
+
+| 参数 | 是否必填 | 说明 |
+| :-------- | :-------- | :-------- |
+| response_type | 是 | 返回类型,这里请填写:`token` |
+| client_id | 是 | 应用 id |
+| redirect_uri | 是 | 用户确认授权后,重定向的url地址 |
+| scope | 否 | 具体请求的权限,多个用逗号(或空格)隔开 |
+| state | 否 | 随机值,此参数会在重定向时追加到url末尾,不填不追加,如果填写则每次填写的值不可以重复 |
+
+此模式会越过授权码的步骤,直接返回 `Access-Token` 到前端页面,形如:
+``` url
+redirect_uri#token=xxxx-xxxx-xxxx-xxxx
+```
+注意 token 是以 `#` 锚参数的形式拼接到 url 上的。
+
+
+## 3、模式三:密码式(Password)
+首先在Client端构建表单,让用户输入 Server 端的账号和密码,然后在 Client 端访问接口
+``` url
+http://{host}:{port}/oauth2/token
+ ?grant_type=password
+ &client_id={client_id}
+ &client_secret={client_secret}
+ &username={username}
+ &password={password}
+ &scope={scope}
+```
+
+参数详解:
+
+| 参数 | 是否必填 | 说明 |
+| :-------- | :-------- | :-------- |
+| grant_type | 是 | 返回类型,这里请填写:`password`|
+| client_id | 是 | 应用 id |
+| client_secret | 是 | 应用秘钥 |
+| username | 是 | 用户的 `OAuth2-Server` 端账号 |
+| password | 是 | 用户的 `OAuth2-Server` 端密码 |
+| scope | 否 | 具体请求的权限,多个用逗号(或空格)隔开 |
+
+接口返回示例:
+
+``` js
+{
+ "code": 200, // 200表示请求成功,非200标识请求失败, 以下不再赘述
+ "msg": "ok",
+ "access_token": "7Ngo1Igg6rieWwAmWMe4cxT7j8o46mjyuabuwLETuAoN6JpPzPO2i3PVpEVJ", // Access-Token 值
+ "refresh_token": "ZMG7QbuCVtCIn1FAJuDbgEjsoXt5Kqzii9zsPeyahAmoir893ARA4rbmeR66", // Refresh-Token 值
+ "expires_in": 7199, // Access-Token 剩余有效期,单位秒
+ "refresh_expires_in": 2591999, // Refresh-Token 剩余有效期,单位秒
+ "client_id": "1001", // 应用 id
+ "scope": "", // 此令牌包含的权限
+}
+```
+
+
+## 4、模式四:凭证式(Client Credentials)
+以上三种模式获取的都是用户的 `Access-Token`,代表用户对第三方应用的授权,
+在OAuth2.0中还有一种针对 Client级别的授权, 即:`Client-Token`,代表应用自身的资源授权
+
+在 Client 端的后台访问以下接口:
+
+``` url
+http://{host}:{port}/oauth2/client_token
+ ?grant_type=client_credentials
+ &client_id={client_id}
+ &client_secret={client_secret}
+ &scope={scope}
+```
+
+参数详解:
+
+| 参数 | 是否必填 | 说明 |
+| :-------- | :-------- | :-------- |
+| grant_type | 是 | 返回类型,这里请填写:`client_credentials`|
+| client_id | 是 | 应用 id |
+| client_secret | 是 | 应用秘钥 |
+| scope | 否 | 具体请求的权限,多个用逗号(或空格)隔开 |
+
+接口返回值样例:
+``` js
+{
+ "code": 200,
+ "msg": "ok",
+ "client_token": "HmzPtaNuIqGrOdudWLzKJRSfPadN497qEJtanYwE7ZvHQWDy0jeoZJuDIiqO", // Client-Token 值
+ "expires_in": 7199, // Token剩余有效时间,单位秒
+ "client_id": "1001", // 应用 id
+ "scope": null // 包含权限
+}
+```
+
+注:`Client-Token`具有延迟作废特性,即:在每次获取最新`Client-Token`的时候,旧`Client-Token`不会立即过期,而是作为`Past-Token`再次储存起来,
+资源请求方只要携带其中之一便可通过Token校验,这种特性保证了在大量并发请求时不会出现“新旧Token交替造成的授权失效”, 保证了服务的高可用。
+
diff --git a/sa-token-doc/oauth2/oauth2-check-domain.md b/sa-token-doc/oauth2/oauth2-check-domain.md
new file mode 100644
index 00000000..f1cca9e3
--- /dev/null
+++ b/sa-token-doc/oauth2/oauth2-check-domain.md
@@ -0,0 +1,93 @@
+# OAuth2 整合-配置域名校验
+
+---
+
+### 1、code 劫持攻击
+在前面章节的 OAuth-Server 搭建示例中:
+
+``` java
+@Component
+public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader {
+ // 根据 clientId 获取 Client 信息
+ @Override
+ public SaClientModel getClientModel(String clientId) {
+ if("1001".equals(clientId)) {
+ return new SaClientModel()
+ // ...
+ .addAllowUrls("*") // 所有允许授权的 url
+ // ...
+ }
+ return null;
+ }
+ // 其它代码 ...
+}
+```
+
+配置项 `AllowUrls` 意为配置此 `Client` 端所有允许的授权地址,不在此配置项中的 URL 将无法下发 `code` 授权码。
+
+为了方便测试,上述代码将其配置为`*`,但是,在生产环境中,此配置项绝对不能配置为 * ,否则会有被 `code` 劫持的风险。
+
+假设攻击者根据模仿我们的授权地址,巧妙的构造一个URL:
+
+> [http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=https://www.baidu.com](http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=https://www.baidu.com)
+
+当不知情的小红被诱导访问了这个 URL 时,它将被重定向至百度首页。
+
+
+
+可以看到,代表着用户身份的 code 授权码也显现到了URL之中,借此漏洞,攻击者完全可以构建一个 URL 将小红的 code 授权码自动提交到攻击者自己的服务器,伪造小红身份登录网站。
+
+
+### 2、防范方法
+
+造成此漏洞的直接原因就是我们对此 client 配置了过于宽泛的 `AllowUrls` 允许授权地址,防范的方法也很简单,就是缩小 `AllowUrls` 授权范围。
+
+我们将其配置为一个具体的URL:
+
+``` java
+@Component
+public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader {
+ // 根据 clientId 获取 Client 信息
+ @Override
+ public SaClientModel getClientModel(String clientId) {
+ if("1001".equals(clientId)) {
+ return new SaClientModel()
+ // ...
+ .addAllowUrls("http://sa-oauth-client.com:8002/") // 所有允许授权的 url
+ // ...
+ }
+ return null;
+ }
+ // 其它代码 ...
+}
+```
+
+再次访问上述链接:
+
+
+
+域名没有通过校验,拒绝授权!
+
+
+### 3、配置安全性参考表
+
+| 配置方式 | 举例 | 安全性 | 建议 |
+| :-------- | :-------- | :-------- | :-------- |
+| 配置为* | `*` | 低 | **禁止在生产环境下使用** |
+| 配置到域名 | `http://sa-oauth-client.com:8002/*` | 中 | 不建议在生产环境下使用 |
+| 配置到详细地址 | `http://sa-oauth-client.com:8002/xxx/xxx` | 高 | 可以在生产环境下使用 |
+
+
+### 4、其它规则
+
+1、AllowUrls 配置的地址不允许出现 `@` 字符。
+*详见源码:[SaOAuth2Template.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java)
+`checkRightUrl` 方法。*
+
+2、AllowUrls 配置的地址 `*` 通配符只允许出现在字符串末尾,不允许出现在字符串中间位置。
+*详见源码: [SaOAuth2Template.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java)
+`checkAllowUrlListStaticMethod` 方法。*
+
+参考:[github/issue/529](https://github.com/dromara/Sa-Token/issues/529)
+感谢这位 `@m4ra7h0n` 用户反馈的漏洞。
+
diff --git a/sa-token-doc/oauth2/oauth2-custom-login.md b/sa-token-doc/oauth2/oauth2-custom-login.md
new file mode 100644
index 00000000..25cb0bb1
--- /dev/null
+++ b/sa-token-doc/oauth2/oauth2-custom-login.md
@@ -0,0 +1,94 @@
+# OAuth2 定制化登录页面
+
+---
+
+
+
+### 1、如何自定义 OAuth-Server 端的登录视图?
+
+重写 `cfg.notLoginView` 策略:
+
+``` java
+@Autowired
+public void configOAuth2Server(SaOAuth2Config cfg) {
+ // 配置:未登录时返回的View
+ cfg.notLoginView = ()->{
+ return new ModelAndView("xxx.html");
+ };
+}
+```
+
+在以上返回的视图中 ajax 方式调用 `/oauth2/doLogin` 接口,该接口接受以下参数:
+
+| 参数 | 是否必填 | 说明 |
+| :-------- | :-------- | :-------- |
+| name | 否 | 账号 |
+| pwd | 否 | 密码 |
+
+接口返回值根据你重写的 `cfg.doLoginHandle` 策略进行自由决定。
+
+
+
+### 2、如何自定义登录API的接口地址?
+根据需求点选择解决方案:
+
+#### 2.1、如果只是想在 doLoginHandle 函数里获取除 name、pwd 以外的参数?
+``` java
+// 在任意代码处获取前端提交的参数
+String xxx = SaHolder.getRequest().getParam("xxx");
+```
+
+#### 2.2、想完全自定义一个接口来接受前端登录请求?
+``` java
+// 直接定义一个拦截路由为 `/oauth2/doLogin` 的接口即可
+@RequestMapping("/oauth2/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.3、不想使用`/oauth2/doLogin`这个接口,想自定义一个API地址?
+
+答:直接在前端更改点击按钮时 Ajax 的请求地址即可
+
+
+
+### 3、如何自定义 OAuth-Server 端的确认授权视图?
+
+重写 `cfg.confirmView` 策略:
+
+``` java
+@Autowired
+public void configOAuth2Server(SaOAuth2Config cfg) {
+ // 配置:授权确认视图
+ cfg.confirmView = (clientId, scopes)->{
+ Map map = new HashMap<>();
+ map.put("clientId", clientId);
+ map.put("scope", scopes);
+ return new ModelAndView("confirm.html", map);
+ };
+}
+```
+
+在以上返回的视图中 ajax 方式调用 `/oauth2/doConfirm` 接口,即可完成授权,该接口接受以下参数:
+
+| 参数 | 是否必填 | 说明 |
+| :-------- | :-------- | :-------- |
+| client_id | 是 | 应用 id |
+| scope | 是 | 具体授予的权限,多个用逗号(或空格)隔开 |
+
+接口返回值样例:
+``` js
+{
+ "code": 200,
+ "msg": "ok",
+ "data": null,
+}
+```
+
+
diff --git a/sa-token-doc/oauth2/oauth2-server.md b/sa-token-doc/oauth2/oauth2-server.md
index 9f2b631f..ad4a1724 100644
--- a/sa-token-doc/oauth2/oauth2-server.md
+++ b/sa-token-doc/oauth2/oauth2-server.md
@@ -11,7 +11,7 @@
### 2、引入依赖
-创建SpringBoot项目 `sa-token-demo-oauth2-server`(不会的同学自行百度或参考仓库示例),添加pom依赖:
+创建SpringBoot项目 `sa-token-demo-oauth2-server`(不会的同学自行百度或参考仓库示例),引入 `pom.xml` 依赖:
@@ -43,36 +43,37 @@ implementation 'cn.dev33:sa-token-oauth2:${sa.top.version}'
### 3、开放服务
-1、新建 `SaOAuth2TemplateImpl`
+1、自定义数据加载器:新建 `SaOAuth2DataLoaderImpl` 实现 `SaOAuth2DataLoader` 接口。
+
``` java
/**
- * Sa-Token OAuth2.0 整合实现
+ * Sa-Token OAuth2:自定义数据加载器
*/
@Component
-public class SaOAuth2TemplateImpl extends SaOAuth2Template {
+public class SaOAuth2DataLoaderImpl implements SaOAuth2DataLoader {
- // 根据 id 获取 Client 信息
+ // 根据 clientId 获取 Client 信息
@Override
public SaClientModel getClientModel(String clientId) {
// 此为模拟数据,真实环境需要从数据库查询
if("1001".equals(clientId)) {
return new SaClientModel()
- .setClientId("1001")
- .setClientSecret("aaaa-bbbb-cccc-dddd-eeee")
- .setAllowUrl("*")
- .setContractScope("userinfo")
- .setIsAutoMode(true);
+ .setClientId("1001") // client id
+ .setClientSecret("aaaa-bbbb-cccc-dddd-eeee") // client 秘钥
+ .addAllowUrls("*") // 所有允许授权的 url
+ .addContractScopes("openid", "userid", "userinfo") // 所有签约的权限
+ .setIsAutoMode(true); // 是否自动判断开放的授权模式
}
return null;
}
- // 根据ClientId 和 LoginId 获取openid
+ // 根据 clientId 和 loginId 获取 openid
@Override
public String getOpenid(String clientId, Object loginId) {
- // 此为模拟数据,真实环境需要从数据库查询
- return "gr_SwoIN0MC1ewxHX_vfCW3BothWDZMMtx__";
+ // 此处使用框架默认算法生成 openid,真实环境建议改为从数据库查询
+ return SaOAuth2DataLoader.super.getOpenid(clientId, loginId);
}
-
+
}
```
@@ -97,30 +98,32 @@ public class SaOAuth2ServerController {
// Sa-OAuth2 定制化配置
@Autowired
public void setSaOAuth2Config(SaOAuth2Config cfg) {
- cfg.
- // 配置:未登录时返回的View
- setNotLoginView(() -> {
- String msg = "当前会话在OAuth-Server端尚未登录,请先访问"
- + " doLogin登录 "
- + "进行登录之后,刷新页面开始授权";
- return msg;
- }).
- // 配置:登录处理函数
- setDoLoginHandle((name, pwd) -> {
- if("sa".equals(name) && "123456".equals(pwd)) {
- StpUtil.login(10001);
- return SaResult.ok();
- }
- return SaResult.error("账号名或密码错误");
- }).
- // 配置:确认授权时返回的View
- setConfirmView((clientId, scope) -> {
- String msg = "应用 " + clientId + " 请求授权:" + scope + "
"
- + "请确认: 确认授权
"
- + "确认之后刷新页面
";
- return msg;
- })
- ;
+
+ // 配置:未登录时返回的View
+ cfg.notLoginView = () -> {
+ String msg = "当前会话在OAuth-Server端尚未登录,请先访问"
+ + " doLogin登录 "
+ + "进行登录之后,刷新页面开始授权";
+ return msg;
+ };
+
+ // 配置:登录处理函数
+ cfg.doLoginHandle = (name, pwd) -> {
+ if("sa".equals(name) && "123456".equals(pwd)) {
+ StpUtil.login(10001);
+ return SaResult.ok();
+ }
+ return SaResult.error("账号名或密码错误");
+ };
+
+ // 配置:确认授权时返回的 view
+ cfg.confirmView = (clientId, scopes) -> {
+ String scopeStr = SaFoxUtil.convertListToString(scopes);
+ String msg = "应用 " + clientId + " 请求授权:" + scopeStr + "
"
+ + "请确认: 确认授权
"
+ + "确认之后刷新页面
";
+ return msg;
+ };
}
// 全局异常拦截
@@ -134,7 +137,19 @@ public class SaOAuth2ServerController {
```
注意:在`setDoLoginHandle`函数里如果要获取name, pwd以外的参数,可通过`SaHolder.getRequest().getParam("xxx")`来获取
-3、创建启动类:
+3、全局异常处理
+``` java
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+ @ExceptionHandler
+ public SaResult handlerException(Exception e) {
+ e.printStackTrace();
+ return SaResult.error(e.getMessage());
+ }
+}
+```
+
+4、创建启动类:
``` java
/**
* 启动:Sa-OAuth2 Server端
@@ -154,28 +169,44 @@ public class SaOAuth2ServerApplication {
1、由于暂未搭建Client端,我们可以使用Sa-Token官网作为重定向URL进行测试:
``` url
-http://sa-oauth-server.com:8001/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=https://sa-token.cc&scope=userinfo
+http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=https://sa-token.cc&scope=openid
```
2、由于首次访问,我们在OAuth-Server端暂未登录,会被转发到登录视图
-
+
3、点击doLogin进行登录之后刷新页面,会提示我们确认授权
-
+
4、点击确认授权之后刷新页面,我们会被重定向至 redirect_uri 页面,并携带了code参数
-
+
4、我们拿着code参数,访问以下地址:
``` url
-http://sa-oauth-server.com:8001/oauth2/token?grant_type=authorization_code&client_id=1001&client_secret=aaaa-bbbb-cccc-dddd-eeee&code={code}
+http://sa-oauth-server.com:8000/oauth2/token?grant_type=authorization_code&client_id=1001&client_secret=aaaa-bbbb-cccc-dddd-eeee&code={code}
```
将得到 `Access-Token`、`Refresh-Token`、`openid`等授权信息
-
+``` js
+{
+ "code": 200,
+ "msg": "ok",
+ "data": null,
+ "token_type": "bearer",
+ "access_token": "cAls8jnBLmeo5yuCUMwb8zxaSsQPPzGINXF3NOCjCqFHplr6hagdT6A5HeR2",
+ "refresh_token": "L2rPbJ3aaOXwaB4Zu0EGWNz5EjVNpw5u2oMP9CS2IEap7rR3Hb76ZqqHS07J",
+ "expires_in": 7199,
+ "refresh_expires_in": 2591999,
+ "client_id": "1001",
+ "scope": "openid",
+ "openid": "ded91dc189a437dd1bac2274be167d50"
+}
+```
+
+
测试完毕
@@ -188,7 +219,7 @@ http://sa-oauth-server.com:8001/oauth2/token?grant_type=authorization_code&clien
依次启动`OAuth2-Server` 与 `OAuth2-Client`,然后从浏览器访问:[http://sa-oauth-client.com:8002](http://sa-oauth-client.com:8002)
-
+
如图,可以针对OAuth2.0四种模式进行详细测试
diff --git a/sa-token-doc/oauth2/readme.md b/sa-token-doc/oauth2/readme.md
index 59d9e4b0..5776fb42 100644
--- a/sa-token-doc/oauth2/readme.md
+++ b/sa-token-doc/oauth2/readme.md
@@ -2,12 +2,12 @@
---
-### 什么是OAuth2.0?解决什么问题?
+### 什么是 OAuth2.0 ?解决什么问题?
-简单来讲,OAuth2.0的应用场景可以理解为单点登录的升级版,单点登录解决了多个系统间会话的共享,OAuth2.0在此基础上增加了应用之间的权限控制
-(SO:有些系统采用OAuth2.0模式实现了单点登录,但这总给人一种“杀鸡焉用宰牛刀”的感觉)
+简单来讲,OAuth2.0的应用场景可以理解为单点登录的升级版,单点登录解决了多个系统间会话的共享,OAuth2.0 在此基础上增加了应用之间的权限控制
+(SO:有些系统采用 OAuth2.0 模式实现了单点登录,但这总给人一种 “杀鸡焉用宰牛刀” 的感觉)
-有关OAuth2.0的设计思想网上教程较多,此处不再重复赘述,详细可参考博客:
+有关 OAuth2.0 的设计思想网上教程较多,此处不再重复赘述,详细可参考博客:
[OAuth2.0 简单解释](https://www.ruanyifeng.com/blog/2019/04/oauth_design.html)
@@ -19,14 +19,40 @@
基于不同的使用场景,OAuth2.0设计了四种模式:
-1. 授权码(Authorization Code):OAuth2.0标准授权步骤,Server端向Client端下放Code码,Client端再用Code码换取授权Token
-2. 隐藏式(Implicit):无法使用授权码模式时的备用选择,Server端使用URL重定向方式直接将Token下放到Client端页面
-3. 密码式(Password):Client直接拿着用户的账号密码换取授权Token
-4. 客户端凭证(Client Credentials):Server端针对Client级别的Token,代表应用自身的资源授权
+1. 授权码(Authorization Code):OAuth2.0 标准授权步骤,Server 端向 Client 端下放 `Code` 码,Client 端再用 `Code` 码换取授权 `Access-Token`。
+2. 隐藏式(Implicit):无法使用授权码模式时的备用选择,Server 端使用 URL 重定向方式直接将 `Access-Token` 下放到 Client 端页面。
+3. 密码式(Password):Client 端直接拿着用户的账号密码换取授权 `Access-Token`。
+4. 客户端凭证(Client Credentials):Server 端针对 Client 级别的 Token,代表应用自身的资源授权。

-接下来我们将通过简单示例演示如何在 Sa-OAuth2 中完成这四种模式的对接: [搭建OAuth2-Server](/oauth2/oauth2-server)
+接下来我们将通过简单示例演示如何在 Sa-Token-OAuth2 中完成这四种模式的对接: [搭建OAuth2-Server](/oauth2/oauth2-server)
+### OAuth2.0 第三方开放平台完整开发流程参考
+
+1. oauth2-server 平台端
+ 1. 搭建 oauth2-server 数据后台管理端(后台人员对底层数据增删改查维护的地方)。
+ 2. 搭建 oauth2-server 数据前台申请端(给第三方公司提供一个申请注册 client 的地方)。
+ 3. 搭建 oauth2-server 授权端 以及其接口文档(让第三方公司拿到 access_token)。
+ 4. 搭建 oauth2-server 资源端 以及其接口文档(让第三方公司通过 access_token 拿到对应的资源数据)。
+ 5. 以上四端可以为一个项目,也可以为四个独立的项目。
+
+2. oauth2-client 第三方公司端
+ 1. 第三方公司登录 oauth-server 数据前台申请端,申请注册应用,拿到 `clientId`、`clientSecret` 等数据。
+ 2. 在自己系统通过 `clientId`、`clientSecret` 等参数对接 oauth2-server 授权端,拿到 `access_token`。
+ 3. 通过 `access_token` 调用 oauth2-server 资源端接口,拿到对应资源数据。
+
+3. 用户端操作
+ 1. 打开第三方公司开发的网站或APP等程序。
+ 2. 一般有个“通过xx第三方登录”的按钮,点它。
+ 3. 跳转到了 oauth2-server 端的网站,在此网站用 oauth2-server 的账号开始登录。
+ 4. 登录完成,继续跳转到授权页,点击确认授权。
+ 5. 授权完成,oauth2-server 端生成一个 code 码,重定向回 oauth2-client 的网站,把 code 参数挂到对应的 url 上。
+ 6. oauth2-client 从 url 中读取 code 参数,提交到 oauth2-client 的后端。
+ 7. oauth2-client 后端拿着 `code`、`clientId`、`clientSecret` 等信息调用 oauth2-server 授权端 的接口,得到 `access_token`。
+ 8. 继续拿着 `access_token` 调用 oauth2-server 资源端获取此用户对应的数据。
+ 9. 一般最终目的拿到一个 openid 值,oauth2-client 根据 openid 进行登录。生成自己的会话 token ,返回到数据到前端。
+ 10. 前端拿到自己 oauth2-client 生成的会话 token ,完成登录。开始进行业务操作。
+
diff --git a/sa-token-doc/sso/sso-apidoc.md b/sa-token-doc/sso/sso-apidoc.md
index 53d47ef4..093a6e4f 100644
--- a/sa-token-doc/sso/sso-apidoc.md
+++ b/sa-token-doc/sso/sso-apidoc.md
@@ -28,6 +28,11 @@ http://{host}:{port}/sso/auth
- 情况一:当前会话在 SSO 认证中心未登录,会进入登录页开始登录。
- 情况二:当前会话在 SSO 认证中心已登录,会被重定向至 `redirect` 地址,并携带 `ticket` 参数。
+`ticket` 码具有以下特点:
+1. 每次授权产生的 `ticket` 码都不一样。
+2. `ticket` 码用完即废,不能二次使用。
+3. 一个 `ticket` 的有效期默认为五分钟,超时自动作废。
+4. 每次授权产生新 `ticket` 码,会导致旧 `ticket` 码立即作废,即使旧 `ticket` 码尚未使用。
### 2、RestAPI 登录接口
``` url
diff --git a/sa-token-doc/use/config.md b/sa-token-doc/use/config.md
index 0e80c9cb..89a97a97 100644
--- a/sa-token-doc/use/config.md
+++ b/sa-token-doc/use/config.md
@@ -297,9 +297,9 @@ sa-token.sso-client.is-slo=true
| 参数名称 | 类型 | 默认值 | 说明 |
| :-------- | :-------- | :-------- | :-------- |
| isCode | Boolean | true | 是否打开模式:授权码(`Authorization Code`) |
-| isImplicit | Boolean | false | 是否打开模式:隐藏式(`Implicit`) |
-| isPassword | Boolean | false | 是否打开模式:密码式(`Password`) |
-| isClient | Boolean | false | 是否打开模式:凭证式(`Client Credentials`) |
+| isImplicit | Boolean | true | 是否打开模式:隐藏式(`Implicit`) |
+| isPassword | Boolean | true | 是否打开模式:密码式(`Password`) |
+| isClient | Boolean | true | 是否打开模式:凭证式(`Client Credentials`) |
| isNewRefresh | Boolean | false | 是否在每次 `Refresh-Token` 刷新 `Access-Token` 时,产生一个新的 Refresh-Token |
| codeTimeout | long | 300 | Code授权码 保存的时间(单位:秒) 默认五分钟 |
| accessTokenTimeout | long | 7200 | `Access-Token` 保存的时间(单位:秒)默认两个小时 |
diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java
index a88b6b7a..7c6f5ff6 100644
--- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java
+++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java
@@ -19,14 +19,18 @@ import cn.dev33.satoken.oauth2.SaOAuth2Manager;
import cn.dev33.satoken.oauth2.consts.SaOAuth2Consts;
import cn.dev33.satoken.oauth2.dao.SaOAuth2Dao;
import cn.dev33.satoken.oauth2.data.convert.SaOAuth2DataConverter;
-import cn.dev33.satoken.oauth2.data.model.*;
-import cn.dev33.satoken.oauth2.data.model.request.RequestAuthModel;
+import cn.dev33.satoken.oauth2.data.model.AccessTokenModel;
+import cn.dev33.satoken.oauth2.data.model.ClientTokenModel;
+import cn.dev33.satoken.oauth2.data.model.CodeModel;
+import cn.dev33.satoken.oauth2.data.model.RefreshTokenModel;
import cn.dev33.satoken.oauth2.data.model.loader.SaClientModel;
+import cn.dev33.satoken.oauth2.data.model.request.RequestAuthModel;
import cn.dev33.satoken.oauth2.error.SaOAuth2ErrorCode;
import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception;
import cn.dev33.satoken.oauth2.strategy.SaOAuth2Strategy;
import cn.dev33.satoken.util.SaFoxUtil;
+import java.util.LinkedHashMap;
import java.util.List;
/**
@@ -211,6 +215,7 @@ public class SaOAuth2DataGenerateDefaultImpl implements SaOAuth2DataGenerate {
String clientTokenValue = SaOAuth2Strategy.instance.createClientToken.execute(clientId, scopes);
ClientTokenModel ct = new ClientTokenModel(clientTokenValue, clientId, scopes);
ct.expiresTime = System.currentTimeMillis() + (cm.getClientTokenTimeout() * 1000);
+ ct.extraData = new LinkedHashMap<>();
SaOAuth2Strategy.instance.workClientTokenByScope.accept(ct);
// 3、保存新Client-Token