初步完善 oauth2 相关文档

This commit is contained in:
click33 2024-08-19 17:49:59 +08:00
parent e8d3a6f137
commit 25b24414ff
17 changed files with 641 additions and 352 deletions

View File

@ -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("/")

View File

@ -42,33 +42,33 @@
<h3>模式一授权码Authorization Code</h3>
<p class="pst">授权码OAuth2.0标准授权流程,先 (重定向) 获取Code授权码再 (Rest API) 获取 Access-Token 和 Openid </p>
<a href="http://sa-oauth-server.com:8001/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/">
<a href="http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/">
<button>点我开始授权登录(静默授权)</button>
</a>
<span class="ps">当请求链接不包含 scope 权限,或请求的 scope 近期已授权时,将无需用户手动确认,做到静默授权</span>
<code>http://sa-oauth-server.com:8001/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/</code>
<code>http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/</code>
<a href="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">
<a href="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">
<button>授权登录(显式授权)</button>
</a>
<span class="ps">当请求链接包含具体的 scope 权限时,将需要用户手动确认,此时 OAuth-Server 会返回更多的数据</span>
<code>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</code>
<code>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</code>
<button onclick="refreshToken()">刷新令牌</button>
<span class="ps">我们可以拿着 Refresh-Token 去刷新我们的 Access-Token每次刷新后旧Token将作废</span>
<code>http://sa-oauth-server.com:8001/oauth2/refresh?grant_type=refresh_token&client_id={value}&client_secret={value}&refresh_token={value}</code>
<code>http://sa-oauth-server.com:8000/oauth2/refresh?grant_type=refresh_token&client_id={value}&client_secret={value}&refresh_token={value}</code>
<button onclick="getUserinfo()">获取账号信息</button>
<span class="ps">使用 Access-Token 置换资源: 获取账号昵称、头像、性别等信息 (Access-Token具备userinfo权限时才可以获取成功) </span>
<code>http://sa-oauth-server.com:8001/oauth2/userinfo?access_token={value}</code>
<code>http://sa-oauth-server.com:8000/oauth2/userinfo?access_token={value}</code>
<br>
<h3>模式二隐藏式Implicit</h3>
<a href="http://sa-oauth-server.com:8001/oauth2/authorize?response_type=token&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=userinfo">
<a href="http://sa-oauth-server.com:8000/oauth2/authorize?response_type=token&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=userinfo">
<button>隐藏式</button>
</a>
<span class="ps">越过授权码的步骤直接返回token到前端页面 格式http//:domain.com#token=xxxx-xxxx </span>
<code>http://sa-oauth-server.com:8001/oauth2/authorize?response_type=token&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=userinfo</code>
<code>http://sa-oauth-server.com:8000/oauth2/authorize?response_type=token&client_id=1001&redirect_uri=http://sa-oauth-client.com:8002/&scope=userinfo</code>
<br>
<h3>模式三密码式Password</h3>
@ -76,7 +76,7 @@
账号:<input name="username">
密码:<input name="password">
<button onclick="passwordLogin()">登录</button>
<code>http://sa-oauth-server.com:8001/oauth2/token?grant_type=password&client_id={value}&client_secret={value}&username={value}&password={value}</code>
<code>http://sa-oauth-server.com:8000/oauth2/token?grant_type=password&client_id={value}&client_secret={value}&username={value}&password={value}</code>
<br>
<h3>模式四凭证式Client Credentials</h3>
@ -87,7 +87,7 @@
保证了服务的高可用</p>
<button onclick="getClientToken()">获取应用Client-Token</button>
<code>http://sa-oauth-server.com:8001/oauth2/client_token?grant_type=client_credentials&client_id={value}&client_secret={value}</code>
<code>http://sa-oauth-server.com:8000/oauth2/client_token?grant_type=client_credentials&client_id={value}&client_secret={value}</code>
<br><br>
<span>更多资料请参考 Sa-Token 官方文档地址:</span>

View File

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

View File

@ -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真实环境建议改为从数据库查询

View File

@ -33,11 +33,12 @@ 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)) {
@ -46,11 +47,12 @@ public class SaOAuth2ServerController {
}
return SaResult.error("账号名或密码错误");
};
// 授权确认视图
cfg.confirmView = (clientId, scope)->{
cfg.confirmView = (clientId, scopes)->{
Map<String, Object> map = new HashMap<>();
map.put("clientId", clientId);
map.put("scope", scope);
map.put("scope", scopes);
return new ModelAndView("confirm.html", map);
};
}

View File

@ -1,5 +1,5 @@
server:
port: 8001
port: 8000
# sa-token配置
sa-token:

View File

@ -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)
- <!-- jwt风格token、使用注解校验权限、OAuth2整个流程参考、state参数详解 -->
- **微服务**
- [分布式Session会话](/micro/dcs-session)

View File

@ -14,6 +14,7 @@ QQ群库经常有小伙伴提问项目需要搭建统一认证中心是用
| 自有系统授权管理 | 支持度高 | 支持度低 |
| Client级的权限校验 | 不支持 | 支持度高 |
| 集成简易度 | 比较简单 | 难度中等 |
| 适合项目 | 企业内部项目整合 | 企业搭建统一认证授权平台,对外开放服务 |
注:以上仅为在 Sa-Token 中两种技术的差异度比较,不同框架的实现可能略有差异,但整体思想是一致的。

View File

@ -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交替造成的授权失效” 保证了服务的高可用

View File

@ -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交替造成的授权失效” 保证了服务的高可用。

View File

@ -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` 授权码。
为了方便测试,上述代码将其配置为`*`,但是,<font color="#FF0000" >在生产环境中,此配置项绝对不能配置为 * </font>,否则会有被 `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 时,它将被重定向至百度首页。
![oauth2-ticket-jc](https://oss.dev33.cn/sa-token/doc/oauth2-new/oauth2-ticket-jc.png 's-w-sh')
可以看到,代表着用户身份的 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;
}
// 其它代码 ...
}
```
再次访问上述链接:
![oauth2-feifa-rf](https://oss.dev33.cn/sa-token/doc/oauth2-new/oauth2-feifa-rf.png 's-w-sh')
域名没有通过校验,拒绝授权!
### 3、配置安全性参考表
| 配置方式 | 举例 | 安全性 | 建议 |
| :-------- | :-------- | :-------- | :-------- |
| 配置为* | `*` | <font color="#F00" ></font> | **<font color="#F00" >禁止在生产环境下使用</font>** |
| 配置到域名 | `http://sa-oauth-client.com:8002/*` | <font color="#F70" ></font> | <font color="#F70" >不建议在生产环境下使用</font> |
| 配置到详细地址 | `http://sa-oauth-client.com:8002/xxx/xxx` | <font color="#080" ></font> | <font color="#080" >可以在生产环境下使用</font> |
### 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` 用户反馈的漏洞。

View File

@ -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<String, Object> 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,
}
```

View File

@ -11,7 +11,7 @@
### 2、引入依赖
创建SpringBoot项目 `sa-token-demo-oauth2-server`(不会的同学自行百度或参考仓库示例),添加pom依赖:
创建SpringBoot项目 `sa-token-demo-oauth2-server`(不会的同学自行百度或参考仓库示例),引入 `pom.xml` 依赖:
<!---------------------------- tabs:start ---------------------------->
<!-------- tab:Maven 方式 -------->
@ -43,34 +43,35 @@ 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(() -> {
cfg.notLoginView = () -> {
String msg = "当前会话在OAuth-Server端尚未登录请先访问"
+ "<a href='/oauth2/doLogin?name=sa&pwd=123456' target='_blank'> doLogin登录 </a>"
+ "进行登录之后,刷新页面开始授权";
return msg;
}).
};
// 配置:登录处理函数
setDoLoginHandle((name, pwd) -> {
cfg.doLoginHandle = (name, pwd) -> {
if("sa".equals(name) && "123456".equals(pwd)) {
StpUtil.login(10001);
return SaResult.ok();
}
return SaResult.error("账号名或密码错误");
}).
// 配置确认授权时返回的View
setConfirmView((clientId, scope) -> {
String msg = "<p>应用 " + clientId + " 请求授权:" + scope + "</p>"
+ "<p>请确认:<a href='/oauth2/doConfirm?client_id=" + clientId + "&scope=" + scope + "' target='_blank'> 确认授权 </a></p>"
};
// 配置:确认授权时返回的 view
cfg.confirmView = (clientId, scopes) -> {
String scopeStr = SaFoxUtil.convertListToString(scopes);
String msg = "<p>应用 " + clientId + " 请求授权:" + scopeStr + "</p>"
+ "<p>请确认:<a href='/oauth2/doConfirm?client_id=" + clientId + "&scope=" + scopeStr + "' target='_blank'> 确认授权 </a></p>"
+ "<p>确认之后刷新页面</p>";
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端暂未登录会被转发到登录视图
![sa-oauth2-server-login-view](https://oss.dev33.cn/sa-token/doc/oauth2/sa-oauth2-server-login-view.png 's-w-sh')
![sa-oauth2-server-login-view](https://oss.dev33.cn/sa-token/doc/oauth2-new/sa-oauth2-server-login-view.png 's-w-sh')
3、点击doLogin进行登录之后刷新页面会提示我们确认授权
![sa-oauth2-server-login-view](https://oss.dev33.cn/sa-token/doc/oauth2/sa-oauth2-server-scope.png 's-w-sh')
![sa-oauth2-server-scope](https://oss.dev33.cn/sa-token/doc/oauth2-new/sa-oauth2-server-scope.png 's-w-sh')
4、点击确认授权之后刷新页面我们会被重定向至 redirect_uri 页面并携带了code参数
![sa-oauth2-server-code](https://oss.dev33.cn/sa-token/doc/oauth2/sa-oauth2-server-code.png 's-w-sh')
![sa-oauth2-server-code](https://oss.dev33.cn/sa-token/doc/oauth2-new/sa-oauth2-server-code.png 's-w-sh')
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`等授权信息
![sa-oauth2-server-token](https://oss.dev33.cn/sa-token/doc/oauth2/sa-oauth2-server-token.png 's-w-sh')
``` 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"
}
```
<!-- ![sa-oauth2-server-token](https://oss.dev33.cn/sa-token/doc/oauth2/sa-oauth2-server-token.png 's-w-sh') -->
测试完毕
@ -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)
![sa-oauth2-client-index](https://oss.dev33.cn/sa-token/doc/oauth2/sa-oauth2-client-index.png 's-w-sh')
![sa-oauth2-client-index](https://oss.dev33.cn/sa-token/doc/oauth2-new/sa-oauth2-client-index.png 's-w-sh')
如图可以针对OAuth2.0四种模式进行详细测试

View File

@ -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)
<!-- 、[OAuth2.0 的四种方式](http://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html) -->
@ -19,14 +19,40 @@
基于不同的使用场景OAuth2.0设计了四种模式:
1. 授权码Authorization CodeOAuth2.0标准授权步骤Server端向Client端下放Code码Client端再用Code码换取授权Token
2. 隐藏式Implicit无法使用授权码模式时的备用选择Server端使用URL重定向方式直接将Token下放到Client端页面
3. 密码式PasswordClient直接拿着用户的账号密码换取授权Token
4. 客户端凭证Client CredentialsServer端针对Client级别的Token代表应用自身的资源授权
1. 授权码Authorization CodeOAuth2.0 标准授权步骤Server 端向 Client 端下放 `Code` Client 端再用 `Code` 码换取授权 `Access-Token`。
2. 隐藏式Implicit无法使用授权码模式时的备用选择Server 端使用 URL 重定向方式直接将 `Access-Token` 下放到 Client 端页面
3. 密码式PasswordClient直接拿着用户的账号密码换取授权 `Access-Token`。
4. 客户端凭证Client CredentialsServer 端针对 Client 级别的 Token代表应用自身的资源授权
![https://oss.dev33.cn/sa-token/doc/oauth2/sa-oauth2-setup.png](https://oss.dev33.cn/sa-token/doc/oauth2/sa-oauth2-setup.png)
接下来我们将通过简单示例演示如何在 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 ,完成登录。开始进行业务操作。

View File

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

View File

@ -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` 保存的时间(单位:秒)默认两个小时 |

View File

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