From bc8339e13d7f15e46eae3cfb0eda9eef6f0ca56d Mon Sep 17 00:00:00 2001
From: click33 <2393584716@qq.com>
Date: Fri, 23 Jul 2021 03:21:03 +0800
Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9EOAuth2.0=E6=8E=A5=E5=8F=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/main/resources/templates/index.html | 9 ++-
.../pj/oauth2/SaOAuth2ServerController.java | 3 +
sa-token-doc/doc/oauth2/oauth2-api.md | 30 +++++++-
.../satoken/oauth2/logic/SaOAuth2Consts.java | 2 +
.../satoken/oauth2/logic/SaOAuth2Handle.java | 31 ++++++++-
.../oauth2/logic/SaOAuth2Template.java | 69 ++++++++++++++++---
.../satoken/oauth2/logic/SaOAuth2Util.java | 46 ++++++++++---
7 files changed, 166 insertions(+), 24 deletions(-)
diff --git a/sa-token-demo/sa-token-demo-oauth2-client/src/main/resources/templates/index.html b/sa-token-demo/sa-token-demo-oauth2-client/src/main/resources/templates/index.html
index ebaef79a..ee520f89 100644
--- a/sa-token-demo/sa-token-demo-oauth2-client/src/main/resources/templates/index.html
+++ b/sa-token-demo/sa-token-demo-oauth2-client/src/main/resources/templates/index.html
@@ -30,6 +30,7 @@
当前Openid:
当前Access-Token:
当前Refresh-Token:
+ 当前令牌包含Scope:
当前Client-Token:
@@ -58,7 +59,7 @@
http://sa-oauth-server.com:8001/oauth2/refresh?grant_type=refresh_token&client_id={value}&client_secret={value}&refresh_token={value}
- 使用 Access-Token 置换资源: 获取账号昵称、头像、性别等信息
+ 使用 Access-Token 置换资源: 获取账号昵称、头像、性别等信息 (Access-Token具备userinfo权限时才可以获取成功)
http://sa-oauth-server.com:8001/oauth2/userinfo?access_token={value}
@@ -202,7 +203,11 @@
data: {accessToken: accessToken},
dataType: 'json',
success: function(res) {
- layer.alert(JSON.stringify(res.data));
+ if(res.code == 200) {
+ layer.alert(JSON.stringify(res.data));
+ } else {
+ layer.alert(res.msg);
+ }
},
error: function(xhr, type, errorThrown){
return layer.alert("异常:" + JSON.stringify(xhr));
diff --git a/sa-token-demo/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java b/sa-token-demo/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java
index e40bc4d0..00b53033 100644
--- a/sa-token-demo/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java
+++ b/sa-token-demo/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java
@@ -76,6 +76,9 @@ public class SaOAuth2ServerController {
Object loginId = SaOAuth2Util.getLoginIdByAccessToken(accessToken);
System.out.println("-------- 此Access-Token对应的账号id: " + loginId);
+ // 校验 Access-Token 是否具有权限: userinfo
+ SaOAuth2Util.checkScope(accessToken, "userinfo");
+
// 模拟账号信息 (真实环境需要查询数据库获取信息)
Map map = new LinkedHashMap();
map.put("nickname", "shengzhang_");
diff --git a/sa-token-doc/doc/oauth2/oauth2-api.md b/sa-token-doc/doc/oauth2/oauth2-api.md
index 4f94652e..2f8cdfbc 100644
--- a/sa-token-doc/doc/oauth2/oauth2-api.md
+++ b/sa-token-doc/doc/oauth2/oauth2-api.md
@@ -105,7 +105,35 @@ http://sa-oauth-server.com:8001/oauth2/refresh
接口返回值同章节1.2,此处不再赘述
-### 1.4、根据 Access-Token 获取相应用户的账号信息
+### 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
diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Consts.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Consts.java
index f84082c6..afd8108e 100644
--- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Consts.java
+++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Consts.java
@@ -15,6 +15,7 @@ public class SaOAuth2Consts {
public static String authorize = "/oauth2/authorize";
public static String token = "/oauth2/token";
public static String refresh = "/oauth2/refresh";
+ public static String revoke = "/oauth2/revoke";
public static String client_token = "/oauth2/client_token";
public static String doLogin = "/oauth2/doLogin";
public static String doConfirm = "/oauth2/doConfirm";
@@ -33,6 +34,7 @@ public class SaOAuth2Consts {
public static String state = "state";
public static String code = "code";
public static String token = "token";
+ public static String access_token = "access_token";
public static String refresh_token = "refresh_token";
public static String grant_type = "grant_type";
public static String username = "username";
diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Handle.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Handle.java
index 9d983f08..fda31bc0 100644
--- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Handle.java
+++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Handle.java
@@ -51,6 +51,11 @@ public class SaOAuth2Handle {
if(req.isPath(Api.refresh) && req.isParam(Param.grant_type, GrantType.refresh_token)) {
return refreshToken(req);
}
+
+ // 回收 Access-Token
+ if(req.isPath(Api.revoke)) {
+ return revokeToken(req);
+ }
// doLogin 登录接口
if(req.isPath(Api.doLogin)) {
@@ -167,10 +172,34 @@ public class SaOAuth2Handle {
SaOAuth2Util.checkRefreshTokenParam(clientId, clientSecret, refreshToken);
// 获取新Token返回
- Object data = SaOAuth2Util.saOAuth2Template.refreshAccessToken(refreshToken).toLineMap();
+ Object data = SaOAuth2Util.refreshAccessToken(refreshToken).toLineMap();
return SaResult.data(data);
}
+ /**
+ * 回收 Access-Token
+ * @param req 请求对象
+ * @return 处理结果
+ */
+ public static Object revokeToken(SaRequest req) {
+ // 获取参数
+ String clientId = req.getParamNotNull(Param.client_id);
+ String clientSecret = req.getParamNotNull(Param.client_secret);
+ String accessToken = req.getParamNotNull(Param.access_token);
+
+ // 如果 Access-Token 不存在,直接返回
+ if(SaOAuth2Util.getAccessToken(accessToken) == null) {
+ return SaResult.ok("access_token不存在:" + accessToken);
+ }
+
+ // 校验参数
+ SaOAuth2Util.checkAccessTokenParam(clientId, clientSecret, accessToken);
+
+ // 获取新Token返回
+ SaOAuth2Util.revokeAccessToken(accessToken);
+ return SaResult.ok();
+ }
+
/**
* doLogin 登录接口
* @param req 请求对象
diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Template.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Template.java
index 12b60fc5..ea93e634 100644
--- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Template.java
+++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Template.java
@@ -41,7 +41,7 @@ public class SaOAuth2Template {
return null;
}
- // ------------------- 资源获取
+ // ------------------- 资源校验API
/**
* 根据id获取Client信息, 如果Client为空,则抛出异常
* @param clientId 应用id
@@ -54,14 +54,6 @@ public class SaOAuth2Template {
}
return clientModel;
}
- /**
- * 获取 access_token 所代表的LoginId
- * @param accessToken access_token
- * @return LoginId
- */
- public Object getLoginIdByAccessToken(String accessToken) {
- return checkAccessToken(accessToken).loginId;
- }
/**
* 获取 Access-Token,如果AccessToken为空则抛出异常
* @param accessToken .
@@ -82,6 +74,29 @@ public class SaOAuth2Template {
SaOAuth2Exception.throwBy(ct == null, "无效:client_token" + ct);
return ct;
}
+ /**
+ * 获取 Access-Token 所代表的LoginId
+ * @param accessToken Access-Token
+ * @return LoginId
+ */
+ public Object getLoginIdByAccessToken(String accessToken) {
+ return checkAccessToken(accessToken).loginId;
+ }
+ /**
+ * 校验:指定 Access-Token 是否具有指定 Scope
+ * @param accessToken Access-Token
+ * @param scopes 需要校验的权限列表
+ */
+ public void checkScope(String accessToken, String... scopes) {
+ if(scopes == null || scopes.length == 0) {
+ return;
+ }
+ AccessTokenModel at = checkAccessToken(accessToken);
+ List scopeList = SaFoxUtil.convertStringToList(at.scope);
+ for (String scope : scopes) {
+ SaOAuth2Exception.throwBy(scopeList.contains(scope) == false, "该 Access-Token 不具备 Scope:" + scope);
+ }
+ }
// ------------------- generate 构建数据
/**
@@ -277,7 +292,28 @@ public class SaOAuth2Template {
}
return url;
}
-
+ /**
+ * 回收 Access-Token
+ * @param accessToken Access-Token值
+ */
+ public void revokeAccessToken(String accessToken) {
+
+ // 如果查不到任何东西, 直接返回
+ AccessTokenModel at = getAccessToken(accessToken);
+ if(at == null) {
+ return;
+ }
+
+ // 删除 Access-Token
+ deleteAccessToken(accessToken);
+ deleteAccessTokenIndex(at.clientId, at.accessToken);
+
+ // 删除对应的 Refresh-Token
+ String refreshToken = getRefreshTokenValue(at.clientId, at.loginId);
+ deleteRefreshToken(refreshToken);
+ deleteRefreshTokenIndex(at.clientId, at.loginId);
+ }
+
// ------------------- check 数据校验
/**
* 判断:指定 loginId 是否对一个 Client 授权给了指定 Scope
@@ -389,6 +425,19 @@ public class SaOAuth2Template {
// 返回Refresh-Token
return rt;
}
+ /**
+ * 校验:Access-Token、clientId、clientSecret 三者是否匹配成功
+ * @param clientId 应用id
+ * @param clientSecret 秘钥
+ * @param accessToken Access-Token
+ * @return SaClientModel对象
+ */
+ public AccessTokenModel checkAccessTokenParam(String clientId, String clientSecret, String accessToken) {
+ AccessTokenModel at = checkAccessToken(accessToken);
+ SaOAuth2Exception.throwBy(at.clientId.equals(clientId) == false, "无效client_id:" + clientId);
+ checkClientSecret(clientId, clientSecret);
+ return at;
+ }
// ------------------- conver 数据转换
/**
diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Util.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Util.java
index 472b055c..068f9001 100644
--- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Util.java
+++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Util.java
@@ -21,7 +21,7 @@ public class SaOAuth2Util {
public static SaOAuth2Template saOAuth2Template = new SaOAuth2Template();
- // ------------------- 资源获取
+ // ------------------- 资源校验API
/**
* 根据id获取Client信息, 如果Client为空,则抛出异常
@@ -32,15 +32,6 @@ public class SaOAuth2Util {
return saOAuth2Template.checkClientModel(clientId);
}
- /**
- * 获取 access_token 所代表的LoginId
- * @param accessToken access_token
- * @return LoginId
- */
- public static Object getLoginIdByAccessToken(String accessToken) {
- return saOAuth2Template.getLoginIdByAccessToken(accessToken);
- }
-
/**
* 获取 Access-Token,如果AccessToken为空则抛出异常
* @param accessToken .
@@ -58,7 +49,24 @@ public class SaOAuth2Util {
public static ClientTokenModel checkClientToken(String clientToken) {
return saOAuth2Template.checkClientToken(clientToken);
}
+
+ /**
+ * 获取 Access-Token 所代表的LoginId
+ * @param accessToken Access-Token
+ * @return LoginId
+ */
+ public static Object getLoginIdByAccessToken(String accessToken) {
+ return saOAuth2Template.getLoginIdByAccessToken(accessToken);
+ }
+ /**
+ * 校验:指定 Access-Token 是否具有指定 Scope
+ * @param accessToken Access-Token
+ * @param scopes 需要校验的权限列表
+ */
+ public static void checkScope(String accessToken, String... scopes) {
+ saOAuth2Template.checkScope(accessToken, scopes);
+ }
// ------------------- generate 构建数据
@@ -141,6 +149,13 @@ public class SaOAuth2Util {
return saOAuth2Template.buildImplicitRedirectUri(redirectUri, token, state);
}
+ /**
+ * 回收 Access-Token
+ * @param accessToken Access-Token值
+ */
+ public static void revokeAccessToken(String accessToken) {
+ saOAuth2Template.revokeAccessToken(accessToken);
+ }
// ------------------- 数据校验
@@ -172,6 +187,7 @@ public class SaOAuth2Util {
public static void checkRightUrl(String clientId, String url) {
saOAuth2Template.checkRightUrl(clientId, url);
}
+
/**
* 校验:clientId 与 clientSecret 是否正确
* @param clientId 应用id
@@ -205,6 +221,16 @@ public class SaOAuth2Util {
return saOAuth2Template.checkRefreshTokenParam(clientId, clientSecret, refreshToken);
}
+ /**
+ * 校验:Access-Token、clientId、clientSecret 三者是否匹配成功
+ * @param clientId 应用id
+ * @param clientSecret 秘钥
+ * @param accessToken Access-Token
+ * @return SaClientModel对象
+ */
+ public static AccessTokenModel checkAccessTokenParam(String clientId, String clientSecret, String accessToken) {
+ return saOAuth2Template.checkAccessTokenParam(clientId, clientSecret, accessToken);
+ }
// ------------------- save 数据