diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/util/SaFoxUtil.java b/sa-token-core/src/main/java/cn/dev33/satoken/util/SaFoxUtil.java
index 8044a223..63bfbb60 100644
--- a/sa-token-core/src/main/java/cn/dev33/satoken/util/SaFoxUtil.java
+++ b/sa-token-core/src/main/java/cn/dev33/satoken/util/SaFoxUtil.java
@@ -106,14 +106,35 @@ public class SaFoxUtil {
/**
* 指定数组是否为null或者空数组
+ *
该方法已过时,建议使用 isEmptyArray 方法
* @param /
* @param array /
* @return /
*/
+ @Deprecated
public static boolean isEmpty(T[] array) {
+ return isEmptyArray(array);
+ }
+
+ /**
+ * 指定数组是否为null或者空数组
+ * @param /
+ * @param array /
+ * @return /
+ */
+ public static boolean isEmptyArray(T[] array) {
return array == null || array.length == 0;
}
+ /**
+ * 指定集合是否为null或者空数组
+ * @param list /
+ * @return /
+ */
+ public static boolean isEmptyList(List> list) {
+ return list == null || list.isEmpty();
+ }
+
/**
* 比较两个对象是否相等
* @param a 第一个对象
@@ -626,6 +647,15 @@ public class SaFoxUtil {
return new ArrayList<>(Arrays.asList(str));
}
+ /**
+ * String 集合转数组
+ * @param list 集合
+ * @return 数组
+ */
+ public static String[] toArray(List list) {
+ return list.toArray(new String[0]);
+ }
+
public static List logLevelList = Arrays.asList("", "trace", "debug", "info", "warn", "error", "fatal");
/**
diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java
index 391a22d6..94a95446 100644
--- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java
+++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/oauth2/SaOAuth2ServerController.java
@@ -75,10 +75,10 @@ public class SaOAuth2ServerController {
String accessToken = SaOAuth2Manager.getDataResolver().readAccessToken(SaHolder.getRequest());
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("userId", loginId); 一般原则下,oauth2-server 不能把 userId 返回给 oauth2-client
diff --git a/sa-token-doc/oauth2/oauth2-check-domain.md b/sa-token-doc/oauth2/oauth2-check-domain.md
index a6e8faf6..841d3ff0 100644
--- a/sa-token-doc/oauth2/oauth2-check-domain.md
+++ b/sa-token-doc/oauth2/oauth2-check-domain.md
@@ -94,7 +94,7 @@ URL 没有通过校验,拒绝授权!
- 反例:`http://*.sa-oauth-client.com/`
*详见源码: [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` 方法。*
+`checkRedirectUriListNormalStaticMethod` 方法。*
参考:[github/issue/529](https://github.com/dromara/Sa-Token/issues/529)
感谢这位 `@m4ra7h0n` 用户反馈的漏洞。
diff --git a/sa-token-doc/oauth2/oauth2-dev.md b/sa-token-doc/oauth2/oauth2-dev.md
index def72b9b..45f7097a 100644
--- a/sa-token-doc/oauth2/oauth2-dev.md
+++ b/sa-token-doc/oauth2/oauth2-dev.md
@@ -1,49 +1,118 @@
# Sa-Token-OAuth2 Server端 二次开发用到的所有函数说明
-官方示例只提供了基本的授权流程,以及userinfo资源的开放,如果您需要开放更多的接口,则二次开发时用到以下相关API方法
+官方示例只提供了基本的授权流程,以及 userinfo 资源的开放,如果您需要开放更多的接口,则二次开发时可能用到以下相关 API 方法
---
-## Sa-OAuth2 模块常用方法
+### Client 信息相关
``` java
-// 根据 id 获取 Client 信息, 如果 Client 为空,则抛出异常
+// 获取 ClientModel,根据 clientId
+SaOAuth2Util.getClientModel(clientId);
+
+// 校验 clientId 信息并返回 ClientModel,如果找不到对应 Client 信息则抛出异常
SaOAuth2Util.checkClientModel(clientId);
-// 获取 Access-Token,如果Access-Token为空则抛出异常
+// 校验:clientId 与 clientSecret 是否正确
+SaOAuth2Util.checkClientSecret(clientId, clientSecret);
+
+// 校验:clientId 与 clientSecret 是否正确,并且是否签约了指定 scopes
+SaOAuth2Util.checkClientSecretAndScope(clientId, clientSecret, scopes);
+
+// 判断:该 Client 是否签约了指定的 Scope,返回 true 或 false
+SaOAuth2Util.isContractScope(clientId, scopes);
+
+// 校验:该 Client 是否签约了指定的 Scope,如果没有则抛出异常
+SaOAuth2Util.checkContractScope(clientId, scopes);
+
+// 校验:该 Client 是否签约了指定的 Scope,如果没有则抛出异常
+SaOAuth2Util.checkContractScope(clientModel, scopes);
+
+// 校验:该 Client 使用指定 url 作为回调地址,是否合法
+SaOAuth2Util.checkRedirectUri(clientId, url);
+
+// 判断:指定 loginId 是否对一个 Client 授权给了指定 Scope
+SaOAuth2Util.isGrantScope(loginId, clientId, scopes);
+```
+
+
+### Access-Token 相关
+``` java
+// 获取 AccessTokenModel,无效的 AccessToken 会返回 null
+SaOAuth2Util.getAccessToken(accessToken);
+
+// 校验 Access-Token,成功返回 AccessTokenModel,失败则抛出异常
SaOAuth2Util.checkAccessToken(accessToken);
-// 获取 Client-Token,如果Client-Token为空则抛出异常
-SaOAuth2Util.checkClientToken(clientToken);
+// 获取 Access-Token,根据索引: clientId、loginId
+SaOAuth2Util.getAccessTokenValue(clientId, loginId);
+
+// 判断:指定 Access-Token 是否具有指定 Scope 列表,返回 true 或 false
+SaOAuth2Util.hasAccessTokenScope(accessToken, ...scopes);
+
+// 校验:指定 Access-Token 是否具有指定 Scope 列表,如果不具备则抛出异常
+SaOAuth2Util.checkAccessTokenScope(accessToken, ...scopes);
// 获取 Access-Token 所代表的LoginId
SaOAuth2Util.getLoginIdByAccessToken(accessToken);
-// 校验:指定 Access-Token 是否具有指定 Scope
-SaOAuth2Util.checkScope(accessToken, scopes);
+// 获取 Access-Token 所代表的 clientId
+SaOAuth2Util.getClientIdByAccessToken(accessToken);
-// 根据 code码 生成 Access-Token
-SaOAuth2Util.generateAccessToken(code);
-
-// 根据 Refresh-Token 生成一个新的 Access-Token
-SaOAuth2Util.refreshAccessToken(refreshToken);
-
-// 构建 Client-Token
-SaOAuth2Util.generateClientToken(clientId, scope);
-
-// 校验 Client-Token 是否含有指定 Scope
-SaOAuth2Util.checkClientTokenScope(clientToken, scopes);
-
-// 回收 Access-Token
+// 回收 Access-Token
SaOAuth2Util.revokeAccessToken(accessToken);
-// 持久化:用户授权记录
-SaOAuth2Util.saveGrantScope(clientId, loginId, scope);
-
-// 获取:Refresh-Token Model
-SaOAuth2Util.getRefreshToken(refreshToken);
+// 回收 Access-Token,根据索引: clientId、loginId
+SaOAuth2Util.revokeAccessTokenByIndex(clientId, loginId);
```
-详情请参考源码:[码云:SaOAuth2Util.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/logic/SaOAuth2Util.java)
+
+### Refresh-Token 相关
+``` java
+// 获取 RefreshTokenModel,无效的 RefreshToken 会返回 null
+SaOAuth2Util.getRefreshToken(refreshToken);
+
+// 校验 Refresh-Token,成功返回 RefreshTokenModel,失败则抛出异常
+SaOAuth2Util.checkRefreshToken(refreshToken);
+
+// 获取 Refresh-Token,根据索引: clientId、loginId
+SaOAuth2Util.getRefreshTokenValue(clientId, Object loginId);
+
+// 根据 RefreshToken 刷新出一个 AccessToken
+SaOAuth2Util.refreshAccessToken(refreshToken);
+```
+
+
+### Client-Token 相关
+
+``` java
+// 获取 ClientTokenModel,无效的 ClientToken 会返回 null
+SaOAuth2Util.getClientToken(clientToken);
+
+// 校验 Client-Token,成功返回 ClientTokenModel,失败则抛出异常
+SaOAuth2Util.checkClientToken(clientToken);
+
+// 获取 ClientToken,根据索引: clientId
+SaOAuth2Util.getClientTokenValue(clientId);
+
+// 判断:指定 Client-Token 是否具有指定 Scope 列表,返回 true 或 false
+SaOAuth2Util.hasClientTokenScope(clientToken, ...scopes);
+
+// 校验:指定 Client-Token 是否具有指定 Scope 列表,如果不具备则抛出异常
+SaOAuth2Util.checkClientTokenScope(clientToken, ...scopes);
+
+// 回收 ClientToken
+SaOAuth2Util.revokeClientToken(clientToken);
+
+// 回收 ClientToken,根据索引: clientId
+SaOAuth2Util.revokeClientTokenByIndex(clientId);
+
+// 回收 PastToken,根据索引: clientId
+SaOAuth2Util.revokePastTokenByIndex(clientId);
+```
+
+---
+
+详情请参考源码:[码云:SaOAuth2Util.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Util.java)
diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerate.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerate.java
index 197dfde7..aa9fe197 100644
--- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerate.java
+++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerate.java
@@ -85,12 +85,6 @@ public interface SaOAuth2DataGenerate {
*/
String buildImplicitRedirectUri(String redirectUri, String token, String state);
- /**
- * 回收 Access-Token
- * @param accessToken Access-Token值
- */
- void revokeAccessToken(String accessToken);
-
/**
* 检查 state 是否被重复使用
* @param state /
diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java
index 266342bf..33ad2055 100644
--- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java
+++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/generate/SaOAuth2DataGenerateDefaultImpl.java
@@ -126,7 +126,7 @@ public class SaOAuth2DataGenerateDefaultImpl implements SaOAuth2DataGenerate {
// 删除旧 Refresh-Token
dao.deleteRefreshToken(rt.refreshToken);
- // 创建并保持新的 Refresh-Token
+ // 创建并保存新的 Refresh-Token
rt = SaOAuth2Manager.getDataConverter().convertRefreshTokenToRefreshToken(rt);
dao.saveRefreshToken(rt);
dao.saveRefreshTokenIndex(rt);
@@ -268,31 +268,6 @@ public class SaOAuth2DataGenerateDefaultImpl implements SaOAuth2DataGenerate {
return url;
}
- /**
- * 回收 Access-Token
- * @param accessToken Access-Token值
- */
- @Override
- public void revokeAccessToken(String accessToken) {
-
- SaOAuth2Dao dao = SaOAuth2Manager.getDao();
-
- // 如果查不到任何东西, 直接返回
- AccessTokenModel at = dao.getAccessToken(accessToken);
- if(at == null) {
- return;
- }
-
- // 删除 Access-Token
- dao.deleteAccessToken(accessToken);
- dao.deleteAccessTokenIndex(at.clientId, at.loginId);
-
- // 删除对应的 Refresh-Token
- String refreshToken = dao.getRefreshTokenValue(at.clientId, at.loginId);
- dao.deleteRefreshToken(refreshToken);
- dao.deleteRefreshTokenIndex(at.clientId, at.loginId);
- }
-
/**
* 检查 state 是否被重复使用
* @param state /
diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/loader/SaOAuth2DataLoader.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/loader/SaOAuth2DataLoader.java
index c8f93ef6..aa8815a7 100644
--- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/loader/SaOAuth2DataLoader.java
+++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/loader/SaOAuth2DataLoader.java
@@ -17,7 +17,8 @@ package cn.dev33.satoken.oauth2.data.loader;
import cn.dev33.satoken.oauth2.SaOAuth2Manager;
import cn.dev33.satoken.oauth2.data.model.loader.SaClientModel;
-import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception;
+import cn.dev33.satoken.oauth2.error.SaOAuth2ErrorCode;
+import cn.dev33.satoken.oauth2.exception.SaOAuth2ClientModelException;
import cn.dev33.satoken.secure.SaSecureUtil;
/**
@@ -47,7 +48,9 @@ public interface SaOAuth2DataLoader {
default SaClientModel getClientModelNotNull(String clientId) {
SaClientModel clientModel = getClientModel(clientId);
if(clientModel == null) {
- throw new SaOAuth2Exception("未找到对应的 Client 信息");
+ throw new SaOAuth2ClientModelException("无效 client_id: " + clientId)
+ .setClientId(clientId)
+ .setCode(SaOAuth2ErrorCode.CODE_30105);
}
return clientModel;
}
diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2AccessTokenException.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2AccessTokenException.java
new file mode 100644
index 00000000..89b6ac65
--- /dev/null
+++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2AccessTokenException.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2020-2099 sa-token.cc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package cn.dev33.satoken.oauth2.exception;
+
+/**
+ * 一个异常:代表 Access-Token 相关错误
+ *
+ * @author click33
+ * @since 1.39.0
+ */
+public class SaOAuth2AccessTokenException extends SaOAuth2Exception {
+
+ /**
+ * 序列化版本号
+ */
+ private static final long serialVersionUID = 6806129545290130114L;
+
+ /**
+ * 一个异常:代表 Access-Token 相关错误
+ * @param cause 根异常原因
+ */
+ public SaOAuth2AccessTokenException(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * 一个异常:代表 Access-Token 相关错误
+ * @param message 异常描述
+ */
+ public SaOAuth2AccessTokenException(String message) {
+ super(message);
+ }
+
+ /**
+ * 具体引起异常的 Access-Token 值
+ */
+ public String accessToken;
+
+ public String getAccessToken() {
+ return accessToken;
+ }
+
+ public SaOAuth2AccessTokenException setAccessToken(String accessToken) {
+ this.accessToken = accessToken;
+ return this;
+ }
+
+ /**
+ * 如果 flag==true,则抛出 message 异常
+ * @param flag 标记
+ * @param message 异常信息
+ * @param code 异常细分码
+ */
+ public static void throwBy(boolean flag, String message, int code) {
+ if(flag) {
+ throw new SaOAuth2AccessTokenException(message).setCode(code);
+ }
+ }
+
+}
diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2AccessTokenScopeException.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2AccessTokenScopeException.java
new file mode 100644
index 00000000..cecc4141
--- /dev/null
+++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2AccessTokenScopeException.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2020-2099 sa-token.cc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package cn.dev33.satoken.oauth2.exception;
+
+/**
+ * 一个异常:代表 Access-Token Scope 相关错误
+ *
+ * @author click33
+ * @since 1.39.0
+ */
+public class SaOAuth2AccessTokenScopeException extends SaOAuth2AccessTokenException {
+
+ /**
+ * 序列化版本号
+ */
+ private static final long serialVersionUID = 6806129545290130114L;
+
+ /**
+ * 一个异常:代表 Access-Token Scope 相关错误
+ * @param cause 根异常原因
+ */
+ public SaOAuth2AccessTokenScopeException(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * 一个异常:代表 Access-Token Scope 相关错误
+ * @param message 异常描述
+ */
+ public SaOAuth2AccessTokenScopeException(String message) {
+ super(message);
+ }
+
+ /**
+ * 具体引起异常的 Access-Token 值
+ */
+ public String accessToken;
+
+ /**
+ * 具体引起异常的 scope 值
+ */
+ public String scope;
+
+ public String getAccessToken() {
+ return accessToken;
+ }
+
+ public SaOAuth2AccessTokenScopeException setAccessToken(String accessToken) {
+ this.accessToken = accessToken;
+ return this;
+ }
+
+ public String getScope() {
+ return scope;
+ }
+
+ public SaOAuth2AccessTokenScopeException setScope(String scope) {
+ this.scope = scope;
+ return this;
+ }
+
+ /**
+ * 如果 flag==true,则抛出 message 异常
+ * @param flag 标记
+ * @param message 异常信息
+ * @param code 异常细分码
+ */
+ public static void throwBy(boolean flag, String message, int code) {
+ if(flag) {
+ throw new SaOAuth2AccessTokenScopeException(message).setCode(code);
+ }
+ }
+
+}
diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2ClientModelException.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2ClientModelException.java
new file mode 100644
index 00000000..59c36238
--- /dev/null
+++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2ClientModelException.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2020-2099 sa-token.cc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package cn.dev33.satoken.oauth2.exception;
+
+/**
+ * 一个异常:代表 ClientModel 相关错误
+ *
+ * @author click33
+ * @since 1.39.0
+ */
+public class SaOAuth2ClientModelException extends SaOAuth2Exception {
+
+ /**
+ * 序列化版本号
+ */
+ private static final long serialVersionUID = 6806129545290130114L;
+
+ /**
+ * 一个异常:代表 ClientModel 相关错误
+ * @param cause 根异常原因
+ */
+ public SaOAuth2ClientModelException(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * 一个异常:代表 ClientModel 相关错误
+ * @param message 异常描述
+ */
+ public SaOAuth2ClientModelException(String message) {
+ super(message);
+ }
+
+ /**
+ * 具体引起异常的 ClientId 值
+ */
+ public String clientId;
+
+ public String getClientId() {
+ return clientId;
+ }
+
+ public SaOAuth2ClientModelException setClientId(String clientId) {
+ this.clientId = clientId;
+ return this;
+ }
+
+ /**
+ * 如果 flag==true,则抛出 message 异常
+ * @param flag 标记
+ * @param message 异常信息
+ * @param code 异常细分码
+ */
+ public static void throwBy(boolean flag, String message, int code) {
+ if(flag) {
+ throw new SaOAuth2ClientModelException(message).setCode(code);
+ }
+ }
+
+ /**
+ * 如果 flag==true,则抛出 message 异常
+ * @param flag 标记
+ * @param message 异常信息
+ * @param clientId 应用id
+ * @param code 异常细分码
+ */
+ public static void throwBy(boolean flag, String message, String clientId, int code) {
+ if(flag) {
+ throw new SaOAuth2ClientModelException(message).setClientId(clientId).setCode(code);
+ }
+ }
+
+}
diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2ClientModelScopeException.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2ClientModelScopeException.java
new file mode 100644
index 00000000..ad1b2507
--- /dev/null
+++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2ClientModelScopeException.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2020-2099 sa-token.cc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package cn.dev33.satoken.oauth2.exception;
+
+/**
+ * 一个异常:代表 ClientModel Scope 相关错误
+ *
+ * @author click33
+ * @since 1.39.0
+ */
+public class SaOAuth2ClientModelScopeException extends SaOAuth2ClientModelException {
+
+ /**
+ * 序列化版本号
+ */
+ private static final long serialVersionUID = 6806129545290130114L;
+
+ /**
+ * 一个异常:代表 ClientModel Scope 相关错误
+ * @param cause 根异常原因
+ */
+ public SaOAuth2ClientModelScopeException(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * 一个异常:代表 ClientModel Scope 相关错误
+ * @param message 异常描述
+ */
+ public SaOAuth2ClientModelScopeException(String message) {
+ super(message);
+ }
+
+ /**
+ * 具体引起异常的 ClientId 值
+ */
+ public String clientId;
+
+ /**
+ * 具体引起异常的 scope 值
+ */
+ public String scope;
+
+ public String getClientId() {
+ return clientId;
+ }
+
+ public SaOAuth2ClientModelScopeException setClientId(String clientId) {
+ this.clientId = clientId;
+ return this;
+ }
+
+ public String getScope() {
+ return scope;
+ }
+
+ public SaOAuth2ClientModelScopeException setScope(String scope) {
+ this.scope = scope;
+ return this;
+ }
+
+ /**
+ * 如果 flag==true,则抛出 message 异常
+ * @param flag 标记
+ * @param message 异常信息
+ * @param code 异常细分码
+ */
+ public static void throwBy(boolean flag, String message, int code) {
+ if(flag) {
+ throw new SaOAuth2ClientModelScopeException(message).setCode(code);
+ }
+ }
+
+}
diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2ClientTokenException.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2ClientTokenException.java
new file mode 100644
index 00000000..2d69dbb8
--- /dev/null
+++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2ClientTokenException.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2020-2099 sa-token.cc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package cn.dev33.satoken.oauth2.exception;
+
+/**
+ * 一个异常:代表 Client-Token 相关错误
+ *
+ * @author click33
+ * @since 1.39.0
+ */
+public class SaOAuth2ClientTokenException extends SaOAuth2Exception {
+
+ /**
+ * 序列化版本号
+ */
+ private static final long serialVersionUID = 6806129545290130114L;
+
+ /**
+ * 一个异常:代表 Client-Token 相关错误
+ * @param cause 根异常原因
+ */
+ public SaOAuth2ClientTokenException(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * 一个异常:代表 Client-Token 相关错误
+ * @param message 异常描述
+ */
+ public SaOAuth2ClientTokenException(String message) {
+ super(message);
+ }
+
+ /**
+ * 具体引起异常的 Client-Token 值
+ */
+ public String clientToken;
+
+ public String getClientToken() {
+ return clientToken;
+ }
+
+ public SaOAuth2ClientTokenException setClientToken(String clientToken) {
+ this.clientToken = clientToken;
+ return this;
+ }
+
+ /**
+ * 如果 flag==true,则抛出 message 异常
+ * @param flag 标记
+ * @param message 异常信息
+ * @param code 异常细分码
+ */
+ public static void throwBy(boolean flag, String message, int code) {
+ if(flag) {
+ throw new SaOAuth2ClientTokenException(message).setCode(code);
+ }
+ }
+
+}
diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2ClientTokenScopeException.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2ClientTokenScopeException.java
new file mode 100644
index 00000000..987dea68
--- /dev/null
+++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2ClientTokenScopeException.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2020-2099 sa-token.cc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package cn.dev33.satoken.oauth2.exception;
+
+/**
+ * 一个异常:代表 Client-Token Scope 相关错误
+ *
+ * @author click33
+ * @since 1.39.0
+ */
+public class SaOAuth2ClientTokenScopeException extends SaOAuth2ClientTokenException {
+
+ /**
+ * 序列化版本号
+ */
+ private static final long serialVersionUID = 6806129545290130114L;
+
+ /**
+ * 一个异常:代表 Client-Token Scope 相关错误
+ * @param cause 根异常原因
+ */
+ public SaOAuth2ClientTokenScopeException(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * 一个异常:代表 Client-Token Scope 相关错误
+ * @param message 异常描述
+ */
+ public SaOAuth2ClientTokenScopeException(String message) {
+ super(message);
+ }
+
+ /**
+ * 具体引起异常的 Client-Token 值
+ */
+ public String clientToken;
+
+ /**
+ * 具体引起异常的 scope 值
+ */
+ public String scope;
+
+ public String getClientToken() {
+ return clientToken;
+ }
+
+ public SaOAuth2ClientTokenScopeException setClientToken(String clientToken) {
+ this.clientToken = clientToken;
+ return this;
+ }
+
+ public String getScope() {
+ return scope;
+ }
+
+ public SaOAuth2ClientTokenScopeException setScope(String scope) {
+ this.scope = scope;
+ return this;
+ }
+
+ /**
+ * 如果 flag==true,则抛出 message 异常
+ * @param flag 标记
+ * @param message 异常信息
+ * @param code 异常细分码
+ */
+ public static void throwBy(boolean flag, String message, int code) {
+ if(flag) {
+ throw new SaOAuth2ClientTokenScopeException(message).setCode(code);
+ }
+ }
+
+}
diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2Exception.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2Exception.java
index c1b96496..04b7d3cc 100644
--- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2Exception.java
+++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2Exception.java
@@ -18,7 +18,7 @@ package cn.dev33.satoken.oauth2.exception;
import cn.dev33.satoken.exception.SaTokenException;
/**
- * 一个异常:代表OAuth2认证流程错误
+ * 一个异常:代表 OAuth2 认证流程错误
*
* @author click33
* @since 1.33.0
@@ -31,7 +31,7 @@ public class SaOAuth2Exception extends SaTokenException {
private static final long serialVersionUID = 6806129545290130114L;
/**
- * 一个异常:代表OAuth2认证流程错误
+ * 一个异常:代表 OAuth2 认证流程错误
* @param cause 根异常原因
*/
public SaOAuth2Exception(Throwable cause) {
@@ -39,7 +39,7 @@ public class SaOAuth2Exception extends SaTokenException {
}
/**
- * 一个异常:代表OAuth2认证流程错误
+ * 一个异常:代表 OAuth2 认证流程错误
* @param message 异常描述
*/
public SaOAuth2Exception(String message) {
@@ -47,7 +47,7 @@ public class SaOAuth2Exception extends SaTokenException {
}
/**
- * 如果flag==true,则抛出message异常
+ * 如果 flag==true,则抛出 message 异常
* @param flag 标记
* @param message 异常信息
* @param code 异常细分码
diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2RefreshTokenException.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2RefreshTokenException.java
new file mode 100644
index 00000000..e110a31e
--- /dev/null
+++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/exception/SaOAuth2RefreshTokenException.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2020-2099 sa-token.cc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package cn.dev33.satoken.oauth2.exception;
+
+/**
+ * 一个异常:代表 Refresh-Token 相关错误
+ *
+ * @author click33
+ * @since 1.39.0
+ */
+public class SaOAuth2RefreshTokenException extends SaOAuth2Exception {
+
+ /**
+ * 序列化版本号
+ */
+ private static final long serialVersionUID = 6806129545290130114L;
+
+ /**
+ * 一个异常:代表 Refresh-Token 相关错误
+ * @param cause 根异常原因
+ */
+ public SaOAuth2RefreshTokenException(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * 一个异常:代表 Refresh-Token 相关错误
+ * @param message 异常描述
+ */
+ public SaOAuth2RefreshTokenException(String message) {
+ super(message);
+ }
+
+ /**
+ * 具体引起异常的 Refresh-Token 值
+ */
+ public String refreshToken;
+
+ public String getRefreshToken() {
+ return refreshToken;
+ }
+
+ public SaOAuth2RefreshTokenException setRefreshToken(String refreshToken) {
+ this.refreshToken = refreshToken;
+ return this;
+ }
+
+ /**
+ * 如果 flag==true,则抛出 message 异常
+ * @param flag 标记
+ * @param message 异常信息
+ * @param code 异常细分码
+ */
+ public static void throwBy(boolean flag, String message, int code) {
+ if(flag) {
+ throw new SaOAuth2RefreshTokenException(message).setCode(code);
+ }
+ }
+
+ /**
+ * 如果 flag==true,则抛出 message 异常
+ * @param flag 标记
+ * @param message 异常信息
+ * @param refreshToken refreshToken
+ * @param code 异常细分码
+ */
+ public static void throwBy(boolean flag, String message, String refreshToken, int code) {
+ if(flag) {
+ throw new SaOAuth2RefreshTokenException(message).setRefreshToken(refreshToken).setCode(code);
+ }
+ }
+
+}
diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java
index 552c46b4..f8e56867 100644
--- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java
+++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/processor/SaOAuth2ServerProcessor.java
@@ -37,6 +37,7 @@ import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception;
import cn.dev33.satoken.oauth2.strategy.SaOAuth2Strategy;
import cn.dev33.satoken.oauth2.template.SaOAuth2Template;
import cn.dev33.satoken.router.SaHttpMethod;
+import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaResult;
import java.util.List;
@@ -130,10 +131,10 @@ public class SaOAuth2ServerProcessor {
RequestAuthModel ra = SaOAuth2Manager.getDataResolver().readRequestAuthModel(req, SaOAuth2Manager.getStpLogic().getLoginId());
// 4、校验:重定向域名是否合法
- oauth2Template.checkRightUrl(ra.clientId, ra.redirectUri);
+ oauth2Template.checkRedirectUri(ra.clientId, ra.redirectUri);
// 5、校验:此次申请的Scope,该Client是否已经签约
- oauth2Template.checkContract(ra.clientId, ra.scopes);
+ oauth2Template.checkContractScope(ra.clientId, ra.scopes);
// 6、判断:如果此次申请的Scope,该用户尚未授权,则转到授权页面
boolean isNeedCarefulConfirm = oauth2Template.isNeedCarefulConfirm(ra.loginId, ra.clientId, ra.scopes);
@@ -205,7 +206,7 @@ public class SaOAuth2ServerProcessor {
oauth2Template.checkAccessTokenParam(clientId, clientSecret, accessToken);
// 回收 Access-Token
- SaOAuth2Manager.getDataGenerate().revokeAccessToken(accessToken);
+ oauth2Template.revokeAccessToken(accessToken);
// 返回
return SaOAuth2Manager.getDataResolver().buildRevokeTokenReturnValue();
@@ -306,7 +307,7 @@ public class SaOAuth2ServerProcessor {
List scopes = SaOAuth2Manager.getDataConverter().convertScopeStringToList(req.getParam(Param.scope));
//校验 ClientScope
- oauth2Template.checkContract(clientId, scopes);
+ oauth2Template.checkContractScope(clientId, scopes);
// 校验 ClientSecret
oauth2Template.checkClientSecret(clientId, clientSecret);
diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java
index f9862b61..e35cef93 100644
--- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java
+++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Template.java
@@ -23,11 +23,10 @@ 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.error.SaOAuth2ErrorCode;
-import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception;
+import cn.dev33.satoken.oauth2.exception.*;
import cn.dev33.satoken.strategy.SaStrategy;
import cn.dev33.satoken.util.SaFoxUtil;
-import java.util.HashSet;
import java.util.List;
/**
@@ -38,91 +37,206 @@ import java.util.List;
*/
public class SaOAuth2Template {
- // ------------------- 数据加载
+ // ----------------- ClientModel 相关 -----------------
/**
- * 根据id获取Client信息
- * @param clientId 应用id
- * @return ClientModel
+ * 获取 ClientModel,根据 clientId
+ *
+ * @param clientId /
+ * @return /
*/
public SaClientModel getClientModel(String clientId) {
return SaOAuth2Manager.getDataLoader().getClientModel(clientId);
}
- // ------------------- 资源校验API
/**
- * 根据id获取Client信息, 如果Client为空,则抛出异常
- * @param clientId 应用id
- * @return ClientModel
+ * 校验 clientId 信息并返回 ClientModel,如果找不到对应 Client 信息则抛出异常
+ * @param clientId /
+ * @return /
*/
public SaClientModel checkClientModel(String clientId) {
SaClientModel clientModel = getClientModel(clientId);
if(clientModel == null) {
- throw new SaOAuth2Exception("无效client_id: " + clientId).setCode(SaOAuth2ErrorCode.CODE_30105);
+ throw new SaOAuth2ClientModelException("无效 client_id: " + clientId)
+ .setClientId(clientId)
+ .setCode(SaOAuth2ErrorCode.CODE_30105);
}
return clientModel;
}
+
/**
- * 获取 Access-Token,如果AccessToken为空则抛出异常
- * @param accessToken .
- * @return .
+ * 校验:clientId 与 clientSecret 是否正确
+ * @param clientId 应用id
+ * @param clientSecret 秘钥
+ * @return SaClientModel对象
*/
- public AccessTokenModel checkAccessToken(String accessToken) {
- AccessTokenModel at = SaOAuth2Manager.getDao().getAccessToken(accessToken);
- SaOAuth2Exception.throwBy(at == null, "无效access_token:" + accessToken, SaOAuth2ErrorCode.CODE_30106);
- return at;
+ public SaClientModel checkClientSecret(String clientId, String clientSecret) {
+ SaClientModel cm = checkClientModel(clientId);
+ SaOAuth2ClientModelException.throwBy(cm.clientSecret == null || ! cm.clientSecret.equals(clientSecret), "无效client_secret: " + clientSecret,
+ clientId, SaOAuth2ErrorCode.CODE_30115);
+ return cm;
}
+
/**
- * 获取 Client-Token,如果ClientToken为空则抛出异常
- * @param clientToken .
- * @return .
+ * 校验:clientId 与 clientSecret 是否正确,并且是否签约了指定 scopes
+ * @param clientId 应用id
+ * @param clientSecret 秘钥
+ * @param scopes 权限
+ * @return SaClientModel对象
*/
- public ClientTokenModel checkClientToken(String clientToken) {
- ClientTokenModel ct = SaOAuth2Manager.getDao().getClientToken(clientToken);
- SaOAuth2Exception.throwBy(ct == null, "无效:client_token" + clientToken, SaOAuth2ErrorCode.CODE_30107);
- return ct;
+ public SaClientModel checkClientSecretAndScope(String clientId, String clientSecret, List scopes) {
+ SaClientModel cm = checkClientSecret(clientId, clientSecret);
+ checkContractScope(cm, scopes);
+ return cm;
}
+
/**
- * 获取 Access-Token 所代表的LoginId
- * @param accessToken Access-Token
- * @return LoginId
+ * 判断:该 Client 是否签约了指定的 Scope,返回 true 或 false
+ * @param clientId 应用id
+ * @param scopes 权限
+ * @return /
*/
- 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 = at.scopes;
- for (String scope : scopes) {
- SaOAuth2Exception.throwBy( ! scopeList.contains(scope), "该 Access-Token 不具备 Scope:" + scope, SaOAuth2ErrorCode.CODE_30108);
- }
- }
- /**
- * 校验:指定 Client-Token 是否具有指定 Scope
- * @param clientToken Client-Token
- * @param scopes 需要校验的权限列表
- */
- public void checkClientTokenScope(String clientToken, String... scopes) {
- if(scopes == null || scopes.length == 0) {
- return;
- }
- ClientTokenModel ct = checkClientToken(clientToken);
- List scopeList = ct.scopes;
- for (String scope : scopes) {
- SaOAuth2Exception.throwBy( ! scopeList.contains(scope), "该 Client-Token 不具备 Scope:" + scope, SaOAuth2ErrorCode.CODE_30109);
+ public boolean isContractScope(String clientId, List scopes) {
+ try {
+ checkContractScope(clientId, scopes);
+ return true;
+ } catch (SaOAuth2ClientModelException e) {
+ return false;
}
}
+ /**
+ * 校验:该 Client 是否签约了指定的 Scope,如果没有则抛出异常
+ * @param clientId 应用id
+ * @param scopes 权限列表
+ * @return /
+ */
+ public SaClientModel checkContractScope(String clientId, List scopes) {
+ return checkContractScope(checkClientModel(clientId), scopes);
+ }
- // ------------------- check 数据校验
+ /**
+ * 校验:该 Client 是否签约了指定的 Scope,如果没有则抛出异常
+ * @param cm 应用
+ * @param scopes 权限列表
+ * @return /
+ */
+ public SaClientModel checkContractScope(SaClientModel cm, List scopes) {
+ if(SaFoxUtil.isEmptyList(scopes)) {
+ return cm;
+ }
+ for (String scope : scopes) {
+ if(! cm.contractScopes.contains(scope)) {
+ throw new SaOAuth2ClientModelScopeException("该 client 暂未签约 scope: " + scope)
+ .setClientId(cm.clientId)
+ .setScope(scope)
+ .setCode(SaOAuth2ErrorCode.CODE_30112);
+ }
+ }
+ return cm;
+ }
+
+ // --------- redirect_uri 相关
+
+ /**
+ * 校验:该 Client 使用指定 url 作为回调地址,是否合法
+ * @param clientId 应用id
+ * @param url 指定url
+ */
+ public void checkRedirectUri(String clientId, String url) {
+ // 1、是否是一个有效的url
+ if( ! SaFoxUtil.isUrl(url)) {
+ throw new SaOAuth2ClientModelException("无效redirect_url:" + url)
+ .setClientId(clientId)
+ .setCode(SaOAuth2ErrorCode.CODE_30113);
+ }
+
+ // 2、截取掉?后面的部分
+ int qIndex = url.indexOf("?");
+ if(qIndex != -1) {
+ url = url.substring(0, qIndex);
+ }
+
+ // 3、不允许出现@字符
+ if(url.contains("@")) {
+ // 为什么不允许出现 @ 字符呢,因为这有可能导致 redirect_url 参数绕过 AllowUrl 列表的校验
+ //
+ // 举个例子 SaClientModel 配置:
+ // allow-url=http://sa-oauth-client.com*
+ //
+ // 开发者原意是为了允许 sa-oauth-client.com 下的所有地址都可以下放 code
+ //
+ // 但是如果攻击者精心构建一个url:
+ // http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com@sa-token.cc
+ //
+ // 那么这个url就会绕过 allow-url 的校验,code 被下发到了第三方服务器地址:
+ // http://sa-token.cc/?code=i8vDfbpqBViMe01QoLY1kHROJWYvv9plBtvTZ6kk77KK0e0U4Xj99NPfSZEYjRul
+ //
+ // 造成了 code 参数劫持
+ // 所以此处需要禁止在 url 中出现 @ 字符
+ //
+ // 这么一刀切的做法,可能会导致一些特殊的正常url也无法通过校验,例如:
+ // http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com/@getInfo
+ //
+ // 但是为了安全起见,这么做还是有必要的
+ throw new SaOAuth2ClientModelException("无效 redirect_url(不允许出现@字符):" + url)
+ .setClientId(clientId);
+ }
+
+ // 4、是否在[允许地址列表]之中
+ SaClientModel clientModel = checkClientModel(clientId);
+ checkRedirectUriListNormal(clientModel.allowRedirectUris);
+ if( ! SaStrategy.instance.hasElement.apply(clientModel.allowRedirectUris, url)) {
+ throw new SaOAuth2ClientModelException("非法 redirect_url: " + url)
+ .setClientId(clientId)
+ .setCode(SaOAuth2ErrorCode.CODE_30114);
+ }
+ }
+
+ /**
+ * 校验配置的 allowRedirectUris 是否合规,如果不合规则抛出异常
+ * @param redirectUriList 待校验的 allow-url 地址列表
+ */
+ public void checkRedirectUriListNormal(List redirectUriList){
+ checkRedirectUriListNormalStaticMethod(redirectUriList);
+ }
+
+ /**
+ * 校验配置的 allowRedirectUris 是否合规,如果不合规则抛出异常,静态方法内部实现
+ * @param redirectUriList 待校验的 allow-url 地址列表
+ */
+ public static void checkRedirectUriListNormalStaticMethod(List redirectUriList){
+ for (String url : redirectUriList) {
+ int index = url.indexOf("*");
+ // 如果配置了 * 字符,则必须出现在最后一位,否则属于无效配置项
+ if(index != -1 && index != url.length() - 1) {
+ // 为什么不允许 * 字符出现在中间位置呢,因为这有可能导致 redirect 参数绕过 allow-url 列表的校验
+ //
+ // 举个例子 SaClientModel 配置:
+ // allow-url=http://*.sa-oauth-client.com/
+ //
+ // 开发者原意是为了允许 sa-oauth-client.com 下的所有子域名都可以下放 ticket
+ // 例如:http://shop.sa-oauth-client.com/
+ //
+ // 但是如果攻击者精心构建一个url:
+ // http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-token.cc/a.sa-oauth-client.com/
+ //
+ // 那么这个 url 就会绕过 allow-url 的校验,ticket 被下发到了第三方服务器地址:
+ // http://sa-token.cc/a.sa-oauth-client.com/?code=v2KKMUFK7dDsMMzXLQ3aWGsyGUjrA0dBB2jeOWrpCnC8b5ScmXXQSv20mIwPK7Cx
+ //
+ // 造成了 ticket 参数劫持
+ // 所以此处需要禁止 allow-url 配置项的中间位置出现 * 字符(出现在末尾是没有问题的)
+ //
+ // 这么一刀切的做法,可能会导致正常场景下的子域名url也无法通过校验,例如:
+ // http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://shop.sa-oauth2-client.com/
+ //
+ // 但是为了安全起见,这么做还是有必要的
+ throw new SaOAuth2Exception("无效的 allow-url 配置(*通配符只允许出现在最后一位):" + url);
+ }
+ }
+ }
+
+ // --------- 授权相关
/**
* 判断:指定 loginId 是否对一个 Client 授权给了指定 Scope
@@ -131,10 +245,9 @@ public class SaOAuth2Template {
* @param scopes 权限
* @return 是否已经授权
*/
- public boolean isGrant(Object loginId, String clientId, List scopes) {
- SaOAuth2Dao dao = SaOAuth2Manager.getDao();
- List grantScopeList = dao.getGrantScope(clientId, loginId);
- return scopes.isEmpty() || new HashSet<>(grantScopeList).containsAll(scopes);
+ public boolean isGrantScope(Object loginId, String clientId, List scopes) {
+ List grantScopeList = SaOAuth2Manager.getDao().getGrantScope(clientId, loginId);
+ return SaFoxUtil.list1ContainList2AllElement(grantScopeList, scopes);
}
/**
@@ -166,143 +279,12 @@ public class SaOAuth2Template {
}
// 根据近期授权记录,判断是否需要确认
- return !isGrant(loginId, clientId, scopes);
+ return !isGrantScope(loginId, clientId, scopes);
}
- /**
- * 校验:该Client是否签约了指定的Scope
- * @param clientId 应用id
- * @param scopes 权限(多个用逗号隔开)
- */
- public void checkContract(String clientId, List scopes) {
- List clientScopeList = checkClientModel(clientId).contractScopes;
- if( ! new HashSet<>(clientScopeList).containsAll(scopes)) {
- throw new SaOAuth2Exception("请求的Scope暂未签约").setCode(SaOAuth2ErrorCode.CODE_30112);
- }
- }
- /**
- * 校验:该Client使用指定url作为回调地址,是否合法
- * @param clientId 应用id
- * @param url 指定url
- */
- public void checkRightUrl(String clientId, String url) {
- // 1、是否是一个有效的url
- if( ! SaFoxUtil.isUrl(url)) {
- throw new SaOAuth2Exception("无效redirect_url:" + url).setCode(SaOAuth2ErrorCode.CODE_30113);
- }
- // 2、截取掉?后面的部分
- int qIndex = url.indexOf("?");
- if(qIndex != -1) {
- url = url.substring(0, qIndex);
- }
+ // --------- 请求数据校验相关
- // 3、不允许出现@字符
- if(url.contains("@")) {
- // 为什么不允许出现 @ 字符呢,因为这有可能导致 redirect_url 参数绕过 AllowUrl 列表的校验
- //
- // 举个例子 SaClientModel 配置:
- // allow-url=http://sa-oauth-client.com*
- //
- // 开发者原意是为了允许 sa-oauth-client.com 下的所有地址都可以下放 code
- //
- // 但是如果攻击者精心构建一个url:
- // http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com@sa-token.cc
- //
- // 那么这个url就会绕过 allow-url 的校验,code 被下发到了第三方服务器地址:
- // http://sa-token.cc/?code=i8vDfbpqBViMe01QoLY1kHROJWYvv9plBtvTZ6kk77KK0e0U4Xj99NPfSZEYjRul
- //
- // 造成了 code 参数劫持
- // 所以此处需要禁止在 url 中出现 @ 字符
- //
- // 这么一刀切的做法,可能会导致一些特殊的正常url也无法通过校验,例如:
- // http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-oauth-client.com/@getInfo
- //
- // 但是为了安全起见,这么做还是有必要的
- throw new SaOAuth2Exception("无效 redirect_url(不允许出现@字符):" + url);
- }
-
- // 4、是否在[允许地址列表]之中
- SaClientModel clientModel = checkClientModel(clientId);
- checkAllowUrlList(clientModel.allowRedirectUris);
- if( ! SaStrategy.instance.hasElement.apply(clientModel.allowRedirectUris, url)) {
- throw new SaOAuth2Exception("非法 redirect_url: " + url).setCode(SaOAuth2ErrorCode.CODE_30114);
- }
- }
-
- /**
- * 校验配置的 AllowUrl 是否合规,如果不合规则抛出异常
- * @param allowUrlList 待校验的 allow-url 地址列表
- */
- public void checkAllowUrlList(List allowUrlList){
- checkAllowUrlListStaticMethod(allowUrlList);
- }
-
- /**
- * 校验配置的 AllowUrl 是否合规,如果不合规则抛出异常
- * @param allowUrlList 待校验的 allow-url 地址列表
- */
- public static void checkAllowUrlListStaticMethod(List allowUrlList){
- for (String url : allowUrlList) {
- int index = url.indexOf("*");
- // 如果配置了 * 字符,则必须出现在最后一位,否则属于无效配置项
- if(index != -1 && index != url.length() - 1) {
- // 为什么不允许 * 字符出现在中间位置呢,因为这有可能导致 redirect 参数绕过 allow-url 列表的校验
- //
- // 举个例子 SaClientModel 配置:
- // allow-url=http://*.sa-oauth-client.com/
- //
- // 开发者原意是为了允许 sa-oauth-client.com 下的所有子域名都可以下放 ticket
- // 例如:http://shop.sa-oauth-client.com/
- //
- // 但是如果攻击者精心构建一个url:
- // http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://sa-token.cc/a.sa-oauth-client.com/
- //
- // 那么这个 url 就会绕过 allow-url 的校验,ticket 被下发到了第三方服务器地址:
- // http://sa-token.cc/a.sa-oauth-client.com/?code=v2KKMUFK7dDsMMzXLQ3aWGsyGUjrA0dBB2jeOWrpCnC8b5ScmXXQSv20mIwPK7Cx
- //
- // 造成了 ticket 参数劫持
- // 所以此处需要禁止 allow-url 配置项的中间位置出现 * 字符(出现在末尾是没有问题的)
- //
- // 这么一刀切的做法,可能会导致正常场景下的子域名url也无法通过校验,例如:
- // http://sa-oauth-server.com:8000/oauth2/authorize?response_type=code&client_id=1001&redirect_uri=http://shop.sa-oauth2-client.com/
- //
- // 但是为了安全起见,这么做还是有必要的
- throw new SaOAuth2Exception("无效的 allow-url 配置(*通配符只允许出现在最后一位):" + url);
- }
- }
- }
-
- /**
- * 校验:clientId 与 clientSecret 是否正确
- * @param clientId 应用id
- * @param clientSecret 秘钥
- * @return SaClientModel对象
- */
- public SaClientModel checkClientSecret(String clientId, String clientSecret) {
- SaClientModel cm = checkClientModel(clientId);
- SaOAuth2Exception.throwBy(cm.clientSecret == null || ! cm.clientSecret.equals(clientSecret),
- "无效client_secret: " + clientSecret, SaOAuth2ErrorCode.CODE_30115);
- return cm;
- }
- /**
- * 校验:clientId 与 clientSecret 是否正确,并且是否签约了指定 scopes
- * @param clientId 应用id
- * @param clientSecret 秘钥
- * @param scopes 权限
- * @return SaClientModel对象
- */
- public SaClientModel checkClientSecretAndScope(String clientId, String clientSecret, List scopes) {
- // 先校验 clientSecret
- SaClientModel cm = checkClientSecret(clientId, clientSecret);
- // 再校验 是否签约
- List clientScopeList = cm.contractScopes;
- if( ! new HashSet<>(clientScopeList).containsAll(scopes)) {
- throw new SaOAuth2Exception("请求的Scope暂未签约").setCode(SaOAuth2ErrorCode.CODE_30116);
- }
- // 返回数据
- return cm;
- }
/**
* 校验:使用 code 获取 token 时提供的参数校验
* @param code 授权码
@@ -317,23 +299,24 @@ public class SaOAuth2Template {
// 校验:Code是否存在
CodeModel cm = dao.getCode(code);
- SaOAuth2Exception.throwBy(cm == null, "无效code: " + code, SaOAuth2ErrorCode.CODE_30117);
+ SaOAuth2Exception.throwBy(cm == null, "无效 code: " + code, SaOAuth2ErrorCode.CODE_30117);
// 校验:ClientId是否一致
- SaOAuth2Exception.throwBy( ! cm.clientId.equals(clientId), "无效client_id: " + clientId, SaOAuth2ErrorCode.CODE_30118);
+ SaOAuth2Exception.throwBy( ! cm.clientId.equals(clientId), "无效 client_id: " + clientId, SaOAuth2ErrorCode.CODE_30118);
// 校验:Secret是否正确
String dbSecret = checkClientModel(clientId).clientSecret;
- SaOAuth2Exception.throwBy(dbSecret == null || ! dbSecret.equals(clientSecret), "无效client_secret: " + clientSecret, SaOAuth2ErrorCode.CODE_30119);
+ SaOAuth2Exception.throwBy(dbSecret == null || ! dbSecret.equals(clientSecret), "无效 client_secret: " + clientSecret, SaOAuth2ErrorCode.CODE_30119);
// 如果提供了redirectUri,则校验其是否与请求Code时提供的一致
if( ! SaFoxUtil.isEmpty(redirectUri)) {
- SaOAuth2Exception.throwBy( ! redirectUri.equals(cm.redirectUri), "无效redirect_uri: " + redirectUri, SaOAuth2ErrorCode.CODE_30120);
+ SaOAuth2Exception.throwBy( ! redirectUri.equals(cm.redirectUri), "无效 redirect_uri: " + redirectUri, SaOAuth2ErrorCode.CODE_30120);
}
// 返回CodeModel
return cm;
}
+
/**
* 校验:使用 Refresh-Token 刷新 Access-Token 时提供的参数校验
* @param clientId 应用id
@@ -347,18 +330,20 @@ public class SaOAuth2Template {
// 校验:Refresh-Token是否存在
RefreshTokenModel rt = dao.getRefreshToken(refreshToken);
- SaOAuth2Exception.throwBy(rt == null, "无效refresh_token: " + refreshToken, SaOAuth2ErrorCode.CODE_30121);
+ SaOAuth2RefreshTokenException.throwBy(rt == null, "无效 refresh_token: " + refreshToken, refreshToken, SaOAuth2ErrorCode.CODE_30121);
// 校验:ClientId是否一致
- SaOAuth2Exception.throwBy( ! rt.clientId.equals(clientId), "无效client_id: " + clientId, SaOAuth2ErrorCode.CODE_30122);
+ SaOAuth2ClientModelException.throwBy( ! rt.clientId.equals(clientId), "无效 client_id: " + clientId, clientId, SaOAuth2ErrorCode.CODE_30122);
// 校验:Secret是否正确
String dbSecret = checkClientModel(clientId).clientSecret;
- SaOAuth2Exception.throwBy(dbSecret == null || ! dbSecret.equals(clientSecret), "无效client_secret: " + clientSecret, SaOAuth2ErrorCode.CODE_30123);
+ SaOAuth2ClientModelException.throwBy(dbSecret == null || ! dbSecret.equals(clientSecret), "无效client_secret: " + clientSecret,
+ clientId, SaOAuth2ErrorCode.CODE_30123);
- // 返回Refresh-Token
+ // 返回 Refresh-Token
return rt;
}
+
/**
* 校验:Access-Token、clientId、clientSecret 三者是否匹配成功
* @param clientId 应用id
@@ -368,30 +353,271 @@ public class SaOAuth2Template {
*/
public AccessTokenModel checkAccessTokenParam(String clientId, String clientSecret, String accessToken) {
AccessTokenModel at = checkAccessToken(accessToken);
- SaOAuth2Exception.throwBy( ! at.clientId.equals(clientId), "无效client_id:" + clientId, SaOAuth2ErrorCode.CODE_30124);
+ SaOAuth2ClientModelException.throwBy( ! at.clientId.equals(clientId), "无效 client_id:" + clientId, clientId, SaOAuth2ErrorCode.CODE_30124);
checkClientSecret(clientId, clientSecret);
return at;
}
+ // ----------------- Access-Token 相关 -----------------
+
/**
- * 回收指定的 ClientToken
+ * 获取 AccessTokenModel,无效的 AccessToken 会返回 null
+ * @param accessToken /
+ * @return /
+ */
+ public AccessTokenModel getAccessToken(String accessToken) {
+ return SaOAuth2Manager.getDao().getAccessToken(accessToken);
+ }
+
+ /**
+ * 校验 Access-Token,成功返回 AccessTokenModel,失败则抛出异常
+ * @param accessToken /
+ * @return /
+ */
+ public AccessTokenModel checkAccessToken(String accessToken) {
+ AccessTokenModel at = SaOAuth2Manager.getDao().getAccessToken(accessToken);
+ if(at == null) {
+ throw new SaOAuth2AccessTokenException("无效 access_token: " + accessToken)
+ .setAccessToken(accessToken)
+ .setCode(SaOAuth2ErrorCode.CODE_30106);
+ }
+ return at;
+ }
+
+ /**
+ * 获取 Access-Token,根据索引: clientId、loginId
+ * @param clientId /
+ * @param loginId /
+ * @return /
+ */
+ public String getAccessTokenValue(String clientId, Object loginId) {
+ return SaOAuth2Manager.getDao().getAccessTokenValue(clientId, loginId);
+ }
+
+ /**
+ * 判断:指定 Access-Token 是否具有指定 Scope 列表,返回 true 或 false
+ * @param accessToken Access-Token
+ * @param scopes 需要校验的权限列表
+ */
+ public boolean hasAccessTokenScope(String accessToken, String... scopes) {
+ try {
+ checkAccessTokenScope(accessToken, scopes);
+ return true;
+ } catch (SaOAuth2AccessTokenException e) {
+ return false;
+ }
+ }
+
+ /**
+ * 校验:指定 Access-Token 是否具有指定 Scope 列表,如果不具备则抛出异常
+ * @param accessToken Access-Token
+ * @param scopes 需要校验的权限列表
+ */
+ public void checkAccessTokenScope(String accessToken, String... scopes) {
+ if(SaFoxUtil.isEmptyArray(scopes)) {
+ return;
+ }
+ ClientTokenModel ct = checkClientToken(accessToken);
+ for (String scope : scopes) {
+ if(! ct.scopes.contains(scope)) {
+ throw new SaOAuth2AccessTokenScopeException("该 access_token 不具备 scope:" + scope)
+ .setAccessToken(accessToken)
+ .setScope(scope)
+ .setCode(SaOAuth2ErrorCode.CODE_30108);
+ }
+ }
+ }
+
+ /**
+ * 获取 Access-Token 所代表的LoginId
+ * @param accessToken Access-Token
+ * @return LoginId
+ */
+ public Object getLoginIdByAccessToken(String accessToken) {
+ return checkAccessToken(accessToken).loginId;
+ }
+
+ /**
+ * 获取 Access-Token 所代表的 clientId
+ * @param accessToken Access-Token
+ * @return LoginId
+ */
+ public Object getClientIdByAccessToken(String accessToken) {
+ return checkAccessToken(accessToken).clientId;
+ }
+
+ /**
+ * 回收 Access-Token
+ * @param accessToken Access-Token值
+ */
+ public void revokeAccessToken(String accessToken) {
+ AccessTokenModel at = getAccessToken(accessToken);
+ if(at == null) {
+ return;
+ }
+
+ // 删 at、索引
+ SaOAuth2Dao dao = SaOAuth2Manager.getDao();
+ dao.deleteAccessToken(accessToken);
+ dao.deleteAccessTokenIndex(at.clientId, at.loginId);
+
+ // 删对应的 rt、索引
+// String rtValue = dao.getRefreshTokenValue(at.clientId, at.loginId);
+// dao.deleteRefreshToken(rtValue);
+// dao.deleteRefreshTokenIndex(at.clientId, at.loginId);
+ }
+
+ /**
+ * 回收 Access-Token,根据索引: clientId、loginId
+ * @param clientId /
+ * @param loginId /
+ */
+ public void revokeAccessTokenByIndex(String clientId, Object loginId) {
+ SaOAuth2Dao dao = SaOAuth2Manager.getDao();
+
+ // 删 at、删索引
+ String accessToken = getAccessTokenValue(clientId, loginId);
+ if(accessToken != null) {
+ dao.deleteAccessToken(accessToken);
+ dao.deleteAccessTokenIndex(clientId, loginId);
+ }
+ }
+
+
+ // ----------------- Refresh-Token 相关 -----------------
+
+ /**
+ * 获取 RefreshTokenModel,无效的 RefreshToken 会返回 null
+ * @param refreshToken /
+ * @return /
+ */
+ public RefreshTokenModel getRefreshToken(String refreshToken) {
+ return SaOAuth2Manager.getDao().getRefreshToken(refreshToken);
+ }
+
+ /**
+ * 校验 Refresh-Token,成功返回 RefreshTokenModel,失败则抛出异常
+ * @param refreshToken /
+ * @return /
+ */
+ public RefreshTokenModel checkRefreshToken(String refreshToken) {
+ RefreshTokenModel rt = SaOAuth2Manager.getDao().getRefreshToken(refreshToken);
+ if(rt == null) {
+ throw new SaOAuth2RefreshTokenException("无效 refresh_token: " + refreshToken)
+ .setRefreshToken(refreshToken)
+ .setCode(SaOAuth2ErrorCode.CODE_30111);
+ }
+ return rt;
+ }
+
+ /**
+ * 获取 Refresh-Token,根据索引: clientId、loginId
+ * @param clientId /
+ * @param loginId /
+ * @return /
+ */
+ public String getRefreshTokenValue(String clientId, Object loginId) {
+ return SaOAuth2Manager.getDao().getRefreshTokenValue(clientId, loginId);
+ }
+
+ /**
+ * 根据 RefreshToken 刷新出一个 AccessToken
+ * @param refreshToken /
+ * @return /
+ */
+ public AccessTokenModel refreshAccessToken(String refreshToken) {
+ return SaOAuth2Manager.getDataGenerate().refreshAccessToken(refreshToken);
+ }
+
+
+ // ----------------- Client-Token 相关 -----------------
+
+ /**
+ * 获取 ClientTokenModel,无效的 ClientToken 会返回 null
+ * @param clientToken /
+ * @return /
+ */
+ public ClientTokenModel getClientToken(String clientToken) {
+ return SaOAuth2Manager.getDao().getClientToken(clientToken);
+ }
+
+ /**
+ * 校验 Client-Token,成功返回 ClientTokenModel,失败则抛出异常
+ * @param clientToken /
+ * @return /
+ */
+ public ClientTokenModel checkClientToken(String clientToken) {
+ ClientTokenModel ct = getClientToken(clientToken);
+ if(ct == null) {
+ throw new SaOAuth2ClientTokenException("无效 client_token: " + clientToken)
+ .setClientToken(clientToken)
+ .setCode(SaOAuth2ErrorCode.CODE_30107);
+ }
+ return ct;
+ }
+
+ /**
+ * 获取 ClientToken,根据索引: clientId
+ * @param clientId /
+ * @return /
+ */
+ public String getClientTokenValue(String clientId) {
+ return SaOAuth2Manager.getDao().getClientTokenValue(clientId);
+ }
+
+ /**
+ * 判断:指定 Client-Token 是否具有指定 Scope 列表,返回 true 或 false
+ * @param clientToken Client-Token
+ * @param scopes 需要校验的权限列表
+ */
+ public boolean hasClientTokenScope(String clientToken, String... scopes) {
+ try {
+ checkClientTokenScope(clientToken, scopes);
+ return true;
+ } catch (SaOAuth2ClientTokenException e) {
+ return false;
+ }
+ }
+
+ /**
+ * 校验:指定 Client-Token 是否具有指定 Scope 列表,如果不具备则抛出异常
+ * @param clientToken Client-Token
+ * @param scopes 需要校验的权限列表
+ */
+ public void checkClientTokenScope(String clientToken, String... scopes) {
+ if(SaFoxUtil.isEmptyArray(scopes)) {
+ return;
+ }
+ ClientTokenModel ct = checkClientToken(clientToken);
+ for (String scope : scopes) {
+ if(! ct.scopes.contains(scope)) {
+ throw new SaOAuth2ClientTokenScopeException("该 client_token 不具备 scope:" + scope)
+ .setClientToken(clientToken)
+ .setScope(scope)
+ .setCode(SaOAuth2ErrorCode.CODE_30109);
+ }
+ }
+ }
+
+ /**
+ * 回收 ClientToken
*
* @param clientToken /
*/
public void revokeClientToken(String clientToken) {
- SaOAuth2Dao dao = SaOAuth2Manager.getDao();
- ClientTokenModel ctModel = dao.getClientToken(clientToken);
- if(ctModel == null) {
+ ClientTokenModel ct = getClientToken(clientToken);
+ if(ct == null) {
return;
}
- // 删 ct、索引
+ // 删 ct、删索引
+ SaOAuth2Dao dao = SaOAuth2Manager.getDao();
dao.deleteClientToken(clientToken);
- dao.deleteClientTokenIndex(ctModel.clientId);
+ dao.deleteClientTokenIndex(ct.clientId);
}
/**
- * 回收指定的 ClientToken,根据索引: clientId
+ * 回收 ClientToken,根据索引: clientId
*
* @param clientId /
*/
@@ -399,33 +625,30 @@ public class SaOAuth2Template {
SaOAuth2Dao dao = SaOAuth2Manager.getDao();
// 删 clientToken
- String clientToken = dao.getClientTokenValue(clientId);
+ String clientToken = getClientTokenValue(clientId);
if(clientToken != null) {
dao.deleteClientToken(clientToken);
dao.deleteClientTokenIndex(clientId);
}
+ }
+ /**
+ * 回收 PastToken,根据索引: clientId
+ *
+ * @param clientId /
+ */
+ public void revokePastTokenByIndex(String clientId) {
+ SaOAuth2Dao dao = SaOAuth2Manager.getDao();
// 删 pastToken
String pastToken = dao.getPastTokenValue(clientId);
if(pastToken != null) {
dao.deletePastToken(pastToken);
dao.deletePastTokenIndex(clientId);
}
-
}
-
- // ------------------- 包装其它 bean 的方法
-
- /**
- * 获取:Access-Token Model
- * @param accessToken /
- * @return /
- */
- public AccessTokenModel getAccessToken(String accessToken) {
- return SaOAuth2Manager.getDao().getAccessToken(accessToken);
- }
+ // ----------------- 包装其它 bean 的方法 -----------------
/**
* 持久化:用户授权记录
@@ -455,8 +678,4 @@ public class SaOAuth2Template {
return SaOAuth2Manager.getDataConverter().convertScopeStringToList(lowerScope);
}
-
-
-
-
}
diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Util.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Util.java
index 223d7dcb..4d667933 100644
--- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Util.java
+++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/template/SaOAuth2Util.java
@@ -18,120 +18,52 @@ package cn.dev33.satoken.oauth2.template;
import cn.dev33.satoken.oauth2.SaOAuth2Manager;
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 java.util.List;
/**
- * Sa-Token-OAuth2 模块 工具类
+ * Sa-Token OAuth2 模块 工具类
*
* @author click33
* @since 1.23.0
*/
public class SaOAuth2Util {
-
- // ------------------- 资源校验API
-
+
+ // ----------------- ClientModel 相关 -----------------
+
/**
- * 根据id获取Client信息, 如果Client为空,则抛出异常
- * @param clientId 应用id
- * @return ClientModel
+ * 获取 ClientModel,根据 clientId
+ *
+ * @param clientId /
+ * @return /
+ */
+ public static SaClientModel getClientModel(String clientId) {
+ return SaOAuth2Manager.getTemplate().getClientModel(clientId);
+ }
+
+ /**
+ * 校验 clientId 信息并返回 ClientModel,如果找不到对应 Client 信息则抛出异常
+ * @param clientId /
+ * @return /
*/
public static SaClientModel checkClientModel(String clientId) {
return SaOAuth2Manager.getTemplate().checkClientModel(clientId);
}
-
- /**
- * 获取 Access-Token,如果AccessToken为空则抛出异常
- * @param accessToken .
- * @return .
- */
- public static AccessTokenModel checkAccessToken(String accessToken) {
- return SaOAuth2Manager.getTemplate().checkAccessToken(accessToken);
- }
-
- /**
- * 获取 Client-Token,如果ClientToken为空则抛出异常
- * @param clientToken .
- * @return .
- */
- public static ClientTokenModel checkClientToken(String clientToken) {
- return SaOAuth2Manager.getTemplate().checkClientToken(clientToken);
- }
-
- /**
- * 获取 Access-Token 所代表的LoginId
- * @param accessToken Access-Token
- * @return LoginId
- */
- public static Object getLoginIdByAccessToken(String accessToken) {
- return SaOAuth2Manager.getTemplate().getLoginIdByAccessToken(accessToken);
- }
-
- /**
- * 校验:指定 Access-Token 是否具有指定 Scope
- * @param accessToken Access-Token
- * @param scopes 需要校验的权限列表
- */
- public static void checkScope(String accessToken, String... scopes) {
- SaOAuth2Manager.getTemplate().checkScope(accessToken, scopes);
- }
-
- /**
- * 校验:指定 Client-Token 是否具有指定 Scope
- * @param clientToken Client-Token
- * @param scopes 需要校验的权限列表
- */
- public static void checkClientTokenScope(String clientToken, String... scopes) {
- SaOAuth2Manager.getTemplate().checkClientTokenScope(clientToken, scopes);
- }
-
- // ------------------- 数据校验
-
- /**
- * 判断:指定 loginId 是否对一个 Client 授权给了指定 Scope
- * @param loginId 账号id
- * @param clientId 应用id
- * @param scopes 权限
- * @return 是否已经授权
- */
- public static boolean isGrant(Object loginId, String clientId, List scopes) {
- return SaOAuth2Manager.getTemplate().isGrant(loginId, clientId, scopes);
- }
-
- /**
- * 校验:该Client是否签约了指定的Scope
- * @param clientId 应用id
- * @param scopes 权限(多个用逗号隔开)
- */
- public static void checkContract(String clientId, List scopes) {
- SaOAuth2Manager.getTemplate().checkContract(clientId, scopes);
- }
-
- /**
- * 校验:该Client使用指定url作为回调地址,是否合法
- * @param clientId 应用id
- * @param url 指定url
- */
- public static void checkRightUrl(String clientId, String url) {
- SaOAuth2Manager.getTemplate().checkRightUrl(clientId, url);
- }
-
/**
* 校验:clientId 与 clientSecret 是否正确
- * @param clientId 应用id
- * @param clientSecret 秘钥
- * @return SaClientModel对象
+ * @param clientId 应用id
+ * @param clientSecret 秘钥
+ * @return SaClientModel对象
*/
public static SaClientModel checkClientSecret(String clientId, String clientSecret) {
return SaOAuth2Manager.getTemplate().checkClientSecret(clientId, clientSecret);
}
-
+
/**
- * 校验:clientId 与 clientSecret 是否正确,并且是否签约了指定 scopes
+ * 校验:clientId 与 clientSecret 是否正确,并且是否签约了指定 scopes
* @param clientId 应用id
* @param clientSecret 秘钥
* @param scopes 权限
@@ -140,43 +72,235 @@ public class SaOAuth2Util {
public static SaClientModel checkClientSecretAndScope(String clientId, String clientSecret, List scopes) {
return SaOAuth2Manager.getTemplate().checkClientSecretAndScope(clientId, clientSecret, scopes);
}
-
+
/**
- * 校验:使用 code 获取 token 时提供的参数校验
- * @param code 授权码
- * @param clientId 应用id
- * @param clientSecret 秘钥
- * @param redirectUri 重定向地址
- * @return CodeModel对象
+ * 判断:该 Client 是否签约了指定的 Scope,返回 true 或 false
+ * @param clientId 应用id
+ * @param scopes 权限
+ * @return /
*/
- public static CodeModel checkGainTokenParam(String code, String clientId, String clientSecret, String redirectUri) {
- return SaOAuth2Manager.getTemplate().checkGainTokenParam(code, clientId, clientSecret, redirectUri);
+ public static boolean isContractScope(String clientId, List scopes) {
+ return SaOAuth2Manager.getTemplate().isContractScope(clientId, scopes);
}
/**
- * 校验:使用 Refresh-Token 刷新 Access-Token 时提供的参数校验
- * @param clientId 应用id
- * @param clientSecret 秘钥
- * @param refreshToken Refresh-Token
- * @return CodeModel对象
+ * 校验:该 Client 是否签约了指定的 Scope,如果没有则抛出异常
+ * @param clientId 应用id
+ * @param scopes 权限列表
+ * @return /
*/
- public static RefreshTokenModel checkRefreshTokenParam(String clientId, String clientSecret, String refreshToken) {
- return SaOAuth2Manager.getTemplate().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 SaOAuth2Manager.getTemplate().checkAccessTokenParam(clientId, clientSecret, accessToken);
+ public static SaClientModel checkContractScope(String clientId, List scopes) {
+ return SaOAuth2Manager.getTemplate().checkContractScope(clientId, scopes);
}
/**
- * 回收指定的 ClientToken
+ * 校验:该 Client 是否签约了指定的 Scope,如果没有则抛出异常
+ * @param cm 应用
+ * @param scopes 权限列表
+ * @return /
+ */
+ public static SaClientModel checkContractScope(SaClientModel cm, List scopes) {
+ return SaOAuth2Manager.getTemplate().checkContractScope(cm, scopes);
+ }
+
+ // --------- redirect_uri 相关
+
+ /**
+ * 校验:该 Client 使用指定 url 作为回调地址,是否合法
+ * @param clientId 应用id
+ * @param url 指定url
+ */
+ public static void checkRedirectUri(String clientId, String url) {
+ SaOAuth2Manager.getTemplate().checkRedirectUri(clientId, url);
+ }
+
+ // --------- 授权相关
+
+ /**
+ * 判断:指定 loginId 是否对一个 Client 授权给了指定 Scope
+ * @param loginId 账号id
+ * @param clientId 应用id
+ * @param scopes 权限
+ * @return 是否已经授权
+ */
+ public static boolean isGrantScope(Object loginId, String clientId, List scopes) {
+ return SaOAuth2Manager.getTemplate().isGrantScope(loginId, clientId, scopes);
+ }
+
+
+ // ----------------- Access-Token 相关 -----------------
+
+ /**
+ * 获取 AccessTokenModel,无效的 AccessToken 会返回 null
+ * @param accessToken /
+ * @return /
+ */
+ public static AccessTokenModel getAccessToken(String accessToken) {
+ return SaOAuth2Manager.getTemplate().getAccessToken(accessToken);
+ }
+
+ /**
+ * 校验 Access-Token,成功返回 AccessTokenModel,失败则抛出异常
+ * @param accessToken /
+ * @return /
+ */
+ public static AccessTokenModel checkAccessToken(String accessToken) {
+ return SaOAuth2Manager.getTemplate().checkAccessToken(accessToken);
+ }
+
+ /**
+ * 获取 Access-Token,根据索引: clientId、loginId
+ * @param clientId /
+ * @param loginId /
+ * @return /
+ */
+ public static String getAccessTokenValue(String clientId, Object loginId) {
+ return SaOAuth2Manager.getTemplate().getAccessTokenValue(clientId, loginId);
+ }
+
+ /**
+ * 判断:指定 Access-Token 是否具有指定 Scope 列表,返回 true 或 false
+ * @param accessToken Access-Token
+ * @param scopes 需要校验的权限列表
+ */
+ public static boolean hasAccessTokenScope(String accessToken, String... scopes) {
+ return SaOAuth2Manager.getTemplate().hasAccessTokenScope(accessToken, scopes);
+ }
+
+ /**
+ * 校验:指定 Access-Token 是否具有指定 Scope 列表,如果不具备则抛出异常
+ * @param accessToken Access-Token
+ * @param scopes 需要校验的权限列表
+ */
+ public static void checkAccessTokenScope(String accessToken, String... scopes) {
+ SaOAuth2Manager.getTemplate().checkAccessTokenScope(accessToken, scopes);
+ }
+
+ /**
+ * 获取 Access-Token 所代表的LoginId
+ * @param accessToken Access-Token
+ * @return LoginId
+ */
+ public static Object getLoginIdByAccessToken(String accessToken) {
+ return SaOAuth2Manager.getTemplate().getLoginIdByAccessToken(accessToken);
+ }
+
+ /**
+ * 获取 Access-Token 所代表的 clientId
+ * @param accessToken Access-Token
+ * @return LoginId
+ */
+ public static Object getClientIdByAccessToken(String accessToken) {
+ return SaOAuth2Manager.getTemplate().getClientIdByAccessToken(accessToken);
+ }
+
+ /**
+ * 回收 Access-Token
+ * @param accessToken Access-Token值
+ */
+ public static void revokeAccessToken(String accessToken) {
+ SaOAuth2Manager.getTemplate().revokeAccessToken(accessToken);
+ }
+
+ /**
+ * 回收 Access-Token,根据索引: clientId、loginId
+ * @param clientId /
+ * @param loginId /
+ */
+ public static void revokeAccessTokenByIndex(String clientId, Object loginId) {
+ SaOAuth2Manager.getTemplate().revokeAccessTokenByIndex(clientId, loginId);
+ }
+
+
+ // ----------------- Refresh-Token 相关 -----------------
+
+ /**
+ * 获取 RefreshTokenModel,无效的 RefreshToken 会返回 null
+ * @param refreshToken /
+ * @return /
+ */
+ public static RefreshTokenModel getRefreshToken(String refreshToken) {
+ return SaOAuth2Manager.getTemplate().getRefreshToken(refreshToken);
+ }
+
+ /**
+ * 校验 Refresh-Token,成功返回 RefreshTokenModel,失败则抛出异常
+ * @param refreshToken /
+ * @return /
+ */
+ public static RefreshTokenModel checkRefreshToken(String refreshToken) {
+ return SaOAuth2Manager.getTemplate().checkRefreshToken(refreshToken);
+ }
+
+ /**
+ * 获取 Refresh-Token,根据索引: clientId、loginId
+ * @param clientId /
+ * @param loginId /
+ * @return /
+ */
+ public static String getRefreshTokenValue(String clientId, Object loginId) {
+ return SaOAuth2Manager.getTemplate().getRefreshTokenValue(clientId, loginId);
+ }
+
+ /**
+ * 根据 RefreshToken 刷新出一个 AccessToken
+ * @param refreshToken /
+ * @return /
+ */
+ public static AccessTokenModel refreshAccessToken(String refreshToken) {
+ return SaOAuth2Manager.getTemplate().refreshAccessToken(refreshToken);
+ }
+
+
+ // ----------------- Client-Token 相关 -----------------
+
+ /**
+ * 获取 ClientTokenModel,无效的 ClientToken 会返回 null
+ * @param clientToken /
+ * @return /
+ */
+ public static ClientTokenModel getClientToken(String clientToken) {
+ return SaOAuth2Manager.getTemplate().getClientToken(clientToken);
+ }
+
+ /**
+ * 校验 Client-Token,成功返回 ClientTokenModel,失败则抛出异常
+ * @param clientToken /
+ * @return /
+ */
+ public static ClientTokenModel checkClientToken(String clientToken) {
+ return SaOAuth2Manager.getTemplate().checkClientToken(clientToken);
+ }
+
+ /**
+ * 获取 ClientToken,根据索引: clientId
+ * @param clientId /
+ * @return /
+ */
+ public static String getClientTokenValue(String clientId) {
+ return SaOAuth2Manager.getTemplate().getClientTokenValue(clientId);
+ }
+
+ /**
+ * 判断:指定 Client-Token 是否具有指定 Scope 列表,返回 true 或 false
+ * @param clientToken Client-Token
+ * @param scopes 需要校验的权限列表
+ */
+ public static boolean hasClientTokenScope(String clientToken, String... scopes) {
+ return SaOAuth2Manager.getTemplate().hasClientTokenScope(clientToken, scopes);
+ }
+
+ /**
+ * 校验:指定 Client-Token 是否具有指定 Scope 列表,如果不具备则抛出异常
+ * @param clientToken Client-Token
+ * @param scopes 需要校验的权限列表
+ */
+ public static void checkClientTokenScope(String clientToken, String... scopes) {
+ SaOAuth2Manager.getTemplate().checkClientTokenScope(clientToken, scopes);
+ }
+
+ /**
+ * 回收 ClientToken
*
* @param clientToken /
*/
@@ -185,7 +309,7 @@ public class SaOAuth2Util {
}
/**
- * 回收指定的 ClientToken,根据索引:clientId
+ * 回收 ClientToken,根据索引: clientId
*
* @param clientId /
*/
@@ -193,65 +317,13 @@ public class SaOAuth2Util {
SaOAuth2Manager.getTemplate().revokeClientTokenByIndex(clientId);
}
- // ------------------- save 数据
-
/**
- * 持久化:用户授权记录
- * @param clientId 应用id
- * @param loginId 账号id
- * @param scopes 权限列表
+ * 回收 PastToken,根据索引: clientId
+ *
+ * @param clientId /
*/
- public static void saveGrantScope(String clientId, Object loginId, List scopes) {
- SaOAuth2Manager.getTemplate().saveGrantScope(clientId, loginId, scopes);
- }
-
-
- // ------------------- get 数据
-
- /**
- * 获取:Code Model
- * @param code .
- * @return .
- */
- public static CodeModel getCode(String code) {
- return SaOAuth2Manager.getDao().getCode(code);
- }
-
- /**
- * 获取:Access-Token Model
- * @param accessToken .
- * @return .
- */
- public static AccessTokenModel getAccessToken(String accessToken) {
- return SaOAuth2Manager.getDao().getAccessToken(accessToken);
- }
-
- /**
- * 获取:Refresh-Token Model
- * @param refreshToken .
- * @return .
- */
- public static RefreshTokenModel getRefreshToken(String refreshToken) {
- return SaOAuth2Manager.getDao().getRefreshToken(refreshToken);
- }
-
- /**
- * 获取:Client-Token Model
- * @param clientToken .
- * @return .
- */
- public static ClientTokenModel getClientToken(String clientToken) {
- return SaOAuth2Manager.getDao().getClientToken(clientToken);
- }
-
- /**
- * 获取:用户授权记录
- * @param clientId 应用id
- * @param loginId 账号id
- * @return 权限
- */
- public static List getGrantScope(String clientId, Object loginId) {
- return SaOAuth2Manager.getDao().getGrantScope(clientId, loginId);
+ public static void revokePastTokenByIndex(String clientId) {
+ SaOAuth2Manager.getTemplate().revokePastTokenByIndex(clientId);
}
}