新增 scope 等级划分,可指定哪些权限需要强制每次手动授权

This commit is contained in:
click33
2024-08-21 13:57:05 +08:00
parent 1bc59dc14c
commit 4aa4941598
16 changed files with 545 additions and 71 deletions
+77 -1
View File
@@ -43,6 +43,82 @@ redirect_uri?code={code}&state={state}
4. 每次授权产生新 `Code` 码,会导致旧 `Code` 码立即作废,即使旧 `Code` 码尚未使用。
<details>
<summary>RestAPI 登录接口:/oauth2/doLogin</summary>
如果用户在 OAuth-Server 端尚未登录,则会被阻塞在登录界面,开始登录,需要在页面上调用`/oauth2/doLogin`完成登录(此接口非 OAuth2 标准协议接口)
``` url
http://{host}:{port}/oauth2/doLogin
?name={name}
&pwd={pwd}
```
参数详解:
| 参数 | 是否必填 | 说明 |
| :-------- | :-------- | :-------- |
| name | 否 | 账号 |
| pwd | 否 | 密码 |
访问此接口将进入自定义的 `cfg.doLoginHandle` 函数开始登录,你只要在此函数内调用 `StpUtil.login(xxx)` 即代表登录成功。
另外需要注意:此接口并非只能携带 `name`、`pwd` 参数,因为你可以在方法里通过 `SaHolder.getRequest().getParam("xxx")` 来获取前端提交的其它参数。
</details>
<details>
<summary>RestAPI 确认授权接口:/oauth2/doConfirm</summary>
如果 oauth-client 端申请的 scope 在 OAuth-Server 端需要用户手动确认授权,则会被阻塞在授权界面,
需要在页面上调用`/oauth2/doConfirm`完成授权(此接口非 OAuth2 标准协议接口)
``` url
http://{host}:{port}/oauth2/doConfirm
?client={value}
&scope={value}
&build_redirect_uri={true|false}
&response_type={value}
&redirect_uri={value}
&state={value}
```
参数详解:
| 参数 | 是否必填 | 说明 |
| :-------- | :-------- | :-------- |
| client_id | 是 | 应用 id |
| scope | 是 | 具体确认的权限,多个用逗号(或空格)隔开 |
| build_redirect_uri | 否 | 是否立即构建 `redirect_uri` 授权地址,取值:true | false |
| response_type | 否 | 取 url 上的 `response_type` 参数来提交 |
| redirect_uri | 否 | 取 url 上的 `redirect_uri` 参数来提交 |
| state | 否 | 取 url 上的 `state` 参数来提交 |
此接口有两种调用方式,一种只提供 `client_id`、`scope` 两个参数,此时返回结果代表是否确认授权成功:
``` js
{
code: 200,
msg: 'ok',
data: null,
}
```
一种是指定 `build_redirect_uri: true`,并同时提供 `client_id`、`scope`、`response_type`、`redirect_uri`、`state` 全部参数,
此时返回结果包括最终的 code 授权地址:
``` js
{
code: 200,
msg: 'ok',
data: null,
redirect_uri: 'http://sa-oauth-client.com:8002/?code=n12TTc1M9REfJVqKm0wewDz0tNZDBhE1A90irOJmxD0zb92pdhUK8NghJfuC'
}
```
前端在 ajax 回调函数中直接使用 `location.href=res.redirect_uri` 跳转即可,无需再重复访问 `/oauth2/authorize` 接口。
</details>
### 1.2、根据授权码获取 Access-Token
获得 `Code` 码后,我们可以通过以下接口,获取到用户的 `Access-Token`、`Refresh-Token` 等信息。
@@ -138,7 +214,7 @@ http://{host}:{port}/oauth2/revoke
### 1.5、根据 Access-Token 获取相应用户的账号信息
注:此接口为官方仓库模拟接口,正式项目中大家可以根据此样例,自定义需要的接口及参数
注:此接口非 OAuth2 标准协议接口,为官方仓库 demo 模拟接口,正式项目中大家可以根据此样例,自定义需要的接口及参数
``` url
http://{host}:{port}/oauth2/userinfo?access_token={access_token}
@@ -139,7 +139,7 @@ http://sa-oauth-server.com:8000/oauth2/authorize
#### 3、code 换 access_token
3、访问上述链接后,得到 `code` 授权码,然后我们拿着 `code` 换 `access_token`
访问上述链接后,得到 `code` 授权码,然后我们拿着 `code` 换 `access_token`
``` url
http://sa-oauth-server.com:8000/oauth2/token
?grant_type=authorization_code
+137
View File
@@ -0,0 +1,137 @@
# OAuth2 - 为 Scope 划分等级
### 1、划分等级
我们可以通过配置文件来为 scope 划分等级
<!---------------------------- tabs:start ---------------------------->
<!------------- tab:yaml 风格 ------------->
``` yaml
# sa-token 配置
sa-token:
# OAuth2.0 配置
oauth2:
# 定义哪些 scope 是高级权限,多个用逗号隔开
higher-scope: openid,userid
# 定义哪些 scope 是低级权限,多个用逗号隔开
lower-scope: userinfo
```
<!------------- tab:properties 风格 ------------->
``` properties
# 定义哪些 scope 是高级权限,多个用逗号隔开
sa-token.oauth2.higher-scope=openid,userid
# 定义哪些 scope 是低级权限,多个用逗号隔开
sa-token.oauth2.lower-scope=userinfo
```
<!---------------------------- tabs:end ---------------------------->
如上所示:
- 通过 `sa-token.oauth2.higher-scope` 配置项指定的 `scope` 将变成 **高级权限**。
- 通过 `sa-token.oauth2.lower-scope` 配置项指定的 `scope` 将变成 **低级权限**。
- 其它未指定的 `scope` 将默认为 **一般权限**。
不同的权限等级其差异主要表现在:oauth2-client 授权时是否需要用户手动确认授权。
| 权限等级 | 申请授权时表现 |
| :-------- | :-------- |
| 高级权限 | 申请授权时:每次都需要用户手动点击确认授权按钮,才会下放 code 授权码 |
| 一般权限 | 申请授权时:如果申请的 scope 用户近期授权过,则静默授权,如果近期未授权过,则需要手动点击确认授权按钮 |
| 低级权限 | 申请授权时:不需要用户手动点击确认授权,程序自动完成静默授权 |
### 2、详细举例
1、如下例子,oauth2-client 申请的 `openid` 权限为**高级权限**,每次都需要用户手动点击确认授权按钮,才会下放 code 授权码。
``` url
http://{host}:{port}/oauth2/authorize
?response_type=code
&client_id=1001
&redirect_uri=http://sa-oauth-client.com:8002/
&scope=openid
```
2、如下例子,oauth2-client 申请的 `userinfo` 权限为**低级权限**,此时不需要用户手动点击确认授权,程序自动完成静默授权。
``` url
http://{host}:{port}/oauth2/authorize
?response_type=code
&client_id=1001
&redirect_uri=http://sa-oauth-client.com:8002/
&scope=userinfo
```
3、如下例子,oauth2-client 申请的 `fans_list` 权限为**一般权限**,首次申请时,需要用户手动点击确认授权,第二次再申请则是静默授权。
``` url
http://{host}:{port}/oauth2/authorize
?response_type=code
&client_id=1001
&redirect_uri=http://sa-oauth-client.com:8002/
&scope=fans_list
```
4、如下例子,oauth2-client 申请的 `openid,userid,userinfo,fans_list` 权限同时包括 **高级权限**、**低级权限**、**一般权限**:
``` url
http://{host}:{port}/oauth2/authorize
?response_type=code
&client_id=1001
&redirect_uri=http://sa-oauth-client.com:8002/
&scope=openid,userid,userinfo,fans_list
```
此时是否需要用户手动点击确认授权按钮?具体规则表现为:
- 如果请求的 scope 列表包括高级权限,则必须用户手动点击确认授权。
- 如果 scope 列表不包括高级权限,则将 scope 列表中的所有低级权限剔除。
- 剔除后的 list 大小如果为零,则直接静默授权通过。
- 剔除后的 list 大小如果不为零,则判断剩余的这些 scope 是否全部已近期授权过:
- 如果是,则静默授权。
- 如果否,则需要用户手动点击确认授权。
### 3、申请高级权限时 `/oauth2/authorize` 无法通过验证
由于申请高级权限时,每次都必须用户手动点击确认授权,`/oauth2/authorize` 路由接口是无法完成权限验证操作的。
此时需要将构建 `redirect_uri` 的动作提前,在 `/oauth2/doConfirm` 确认授权接口时额外追加 `build_redirect_uri: true` 等参数:
``` url
http://{host}:{port}/oauth2/doConfirm
?client={value}
&scope={value}
&build_redirect_uri=true
&response_type={value}
&redirect_uri={value}
&state={value}
```
返回结果示例:
``` js
{
code: 200,
msg: 'ok',
data: null,
redirect_uri: 'http://sa-oauth-client.com:8002/?code=n12TTc1M9REfJVqKm0wewDz0tNZDBhE1A90irOJmxD0zb92pdhUK8NghJfuC'
}
```
其中 `redirect_uri` 参数为授权挂载code地址,直接在 ajax 回调函数中使用 `location.href=res.redirect_uri` 跳转即可。
自定义确认授权视图修改参考:
``` java
// 授权确认视图
cfg.confirmView = (clientId, scopes)->{
String scopeStr = SaFoxUtil.convertListToString(scopes);
String yesCode =
"fetch('/oauth2/doConfirm' + location.search + '&build_redirect_uri=true', {method: 'POST'})" +
".then(res => res.json())" +
".then(res => location.href=res.redirect_uri)";
String res = "<p>应用 " + clientId + " 请求授权:" + scopeStr + ",是否同意?</p>"
+ "<p>" +
" <button onclick=\"" + yesCode + "\">同意</button>" +
" <button onclick='history.back()'>拒绝</button>" +
"</p>";
return res;
};
```
+10 -4
View File
@@ -119,10 +119,16 @@ public class SaOAuth2ServerController {
// 配置:确认授权时返回的 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;
String yesCode =
"fetch('/oauth2/doConfirm?client_id=" + clientId + "&scope=" + scopeStr + "', {method: 'POST'})" +
".then(res => res.json())" +
".then(res => location.reload())";
String res = "<p>应用 " + clientId + " 请求授权:" + scopeStr + ",是否同意?</p>"
+ "<p>" +
" <button onclick=\"" + yesCode + "\">同意</button>" +
" <button onclick='history.back()'>拒绝</button>" +
"</p>";
return res;
};
}