mirror of
https://gitee.com/dromara/sa-token.git
synced 2025-06-28 13:34:18 +08:00
初步完善 oauth2 相关文档
This commit is contained in:
parent
e8d3a6f137
commit
25b24414ff
@ -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("/")
|
||||
|
@ -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>
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
@ -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,真实环境建议改为从数据库查询
|
||||
|
@ -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<String, Object> map = new HashMap<>();
|
||||
map.put("clientId", clientId);
|
||||
map.put("scope", scope);
|
||||
map.put("scope", scopes);
|
||||
return new ModelAndView("confirm.html", map);
|
||||
};
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
server:
|
||||
port: 8001
|
||||
port: 8000
|
||||
|
||||
# sa-token配置
|
||||
sa-token:
|
||||
|
@ -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)
|
||||
|
@ -14,6 +14,7 @@ QQ群库经常有小伙伴提问:项目需要搭建统一认证中心,是用
|
||||
| 自有系统授权管理 | 支持度高 | 支持度低 |
|
||||
| Client级的权限校验 | 不支持 | 支持度高 |
|
||||
| 集成简易度 | 比较简单 | 难度中等 |
|
||||
| 适合项目 | 企业内部项目整合 | 企业搭建统一认证授权平台,对外开放服务 |
|
||||
|
||||
|
||||
注:以上仅为在 Sa-Token 中两种技术的差异度比较,不同框架的实现可能略有差异,但整体思想是一致的。
|
||||
|
@ -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交替造成的授权失效”, 保证了服务的高可用
|
||||
|
266
sa-token-doc/oauth2/oauth2-apidoc.md
Normal file
266
sa-token-doc/oauth2/oauth2-apidoc.md
Normal 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交替造成的授权失效”, 保证了服务的高可用。
|
||||
|
93
sa-token-doc/oauth2/oauth2-check-domain.md
Normal file
93
sa-token-doc/oauth2/oauth2-check-domain.md
Normal 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 时,它将被重定向至百度首页。
|
||||
|
||||

|
||||
|
||||
可以看到,代表着用户身份的 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、配置安全性参考表
|
||||
|
||||
| 配置方式 | 举例 | 安全性 | 建议 |
|
||||
| :-------- | :-------- | :-------- | :-------- |
|
||||
| 配置为* | `*` | <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` 用户反馈的漏洞。
|
||||
|
94
sa-token-doc/oauth2/oauth2-custom-login.md
Normal file
94
sa-token-doc/oauth2/oauth2-custom-login.md
Normal 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,
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -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,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端尚未登录,请先访问"
|
||||
+ "<a href='/oauth2/doLogin?name=sa&pwd=123456' target='_blank'> doLogin登录 </a>"
|
||||
+ "进行登录之后,刷新页面开始授权";
|
||||
return msg;
|
||||
}).
|
||||
// 配置:登录处理函数
|
||||
setDoLoginHandle((name, pwd) -> {
|
||||
if("sa".equals(name) && "123456".equals(pwd)) {
|
||||
StpUtil.login(10001);
|
||||
return SaResult.ok();
|
||||
}
|
||||
return SaResult.error("账号名或密码错误");
|
||||
}).
|
||||
// 配置:确认授权时返回的View
|
||||
setConfirmView((clientId, scope) -> {
|
||||
String msg = "<p>应用 " + clientId + " 请求授权:" + scope + "</p>"
|
||||
+ "<p>请确认:<a href='/oauth2/doConfirm?client_id=" + clientId + "&scope=" + scope + "' target='_blank'> 确认授权 </a></p>"
|
||||
+ "<p>确认之后刷新页面</p>";
|
||||
return msg;
|
||||
})
|
||||
;
|
||||
|
||||
// 配置:未登录时返回的View
|
||||
cfg.notLoginView = () -> {
|
||||
String msg = "当前会话在OAuth-Server端尚未登录,请先访问"
|
||||
+ "<a href='/oauth2/doLogin?name=sa&pwd=123456' target='_blank'> doLogin登录 </a>"
|
||||
+ "进行登录之后,刷新页面开始授权";
|
||||
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 = "<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端暂未登录,会被转发到登录视图
|
||||
|
||||

|
||||

|
||||
|
||||
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四种模式进行详细测试
|
||||
|
||||
|
@ -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 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 ,完成登录。开始进行业务操作。
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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` 保存的时间(单位:秒)默认两个小时 |
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user