From f1089f697c672ae03f7e26be3d1c2fbdc283536f Mon Sep 17 00:00:00 2001 From: click33 <2393584716@qq.com> Date: Tue, 13 May 2025 02:38:31 +0800 Subject: [PATCH] =?UTF-8?q?feat(oauth2):=20=E6=96=B0=E5=A2=9E=EF=BC=9A?= =?UTF-8?q?=E5=A4=9A=20Access-Token=20=E5=B9=B6=E5=AD=98=20&=20=E5=A4=9A?= =?UTF-8?q?=20Refresh-Token=20=E5=B9=B6=E5=AD=98=20&=20=E5=A4=9A=20Client-?= =?UTF-8?q?Token=20=E5=B9=B6=E5=AD=98=20&=20=E7=A7=BB=E9=99=A4=20Lower-Cli?= =?UTF-8?q?ent-Token=20=E6=A8=A1=E5=9D=97=20Closes:=20#IBHFD1,=20#IBLL4Q?= =?UTF-8?q?=20fix:=20#724?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/pj/mock/SaClientMockDao.java | 9 +- .../java/com/pj/test/Test2Controller.java | 83 ++ .../dev33/satoken/oauth2/SaOAuth2Manager.java | 3 +- .../oauth2/config/SaOAuth2ServerConfig.java | 64 ++ .../dev33/satoken/oauth2/dao/SaOAuth2Dao.java | 916 ++++++++++++------ .../oauth2/dao/SaOAuth2DaoDefaultImpl.java | 26 - .../data/generate/SaOAuth2DataGenerate.java | 2 +- .../SaOAuth2DataGenerateDefaultImpl.java | 49 +- .../data/model/loader/SaClientModel.java | 152 ++- .../oauth2/template/SaOAuth2Template.java | 88 +- .../satoken/oauth2/template/SaOAuth2Util.java | 42 +- 11 files changed, 959 insertions(+), 475 deletions(-) create mode 100644 sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/test/Test2Controller.java delete mode 100644 sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/dao/SaOAuth2DaoDefaultImpl.java diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/mock/SaClientMockDao.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/mock/SaClientMockDao.java index f2162a91..ff881b8c 100644 --- a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/mock/SaClientMockDao.java +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/mock/SaClientMockDao.java @@ -16,12 +16,14 @@ import java.util.List; @Component public class SaClientMockDao { - public List list = new ArrayList<>(); + public List list; /** * 构造方法,添加三个模拟应用 */ - public SaClientMockDao(){ + public void init(){ + list = new ArrayList<>(); + // 模拟应用1 SaClientModel client1 = new SaClientModel() .setClientId("1001") // client id @@ -77,6 +79,9 @@ public class SaClientMockDao { * @return 应用对象 */ public SaClientModel getClientModel(String clientId) { + if(list == null) { + init(); + } return list.stream() .filter(e -> e.getClientId().equals(clientId)) .findFirst() diff --git a/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/test/Test2Controller.java b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/test/Test2Controller.java new file mode 100644 index 00000000..93710e0e --- /dev/null +++ b/sa-token-demo/sa-token-demo-oauth2/sa-token-demo-oauth2-server/src/main/java/com/pj/test/Test2Controller.java @@ -0,0 +1,83 @@ +package com.pj.test; + +import cn.dev33.satoken.oauth2.template.SaOAuth2Util; +import cn.dev33.satoken.util.SaResult; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * 测试 OAuth2 相关 token 增删查 + * + * @author click33 + * @since 2024/8/25 + */ +@RestController +@RequestMapping("/test") +public class Test2Controller { + + // 测试:查询全部 Access-Token --- http://localhost:8000/test/getAccessTokenValueList?clientId=1001&loginId=10001 + @RequestMapping("/getAccessTokenValueList") + public SaResult getAccessTokenValueList(String clientId, long loginId) { + List accessTokenValueList = SaOAuth2Util.getAccessTokenValueList(clientId, loginId); + return SaResult.data(accessTokenValueList); + } + + // 测试:回收指定 Access-Token --- http://localhost:8000/test/revokeAccessToken?access_token=xxxxxxxxxx + @RequestMapping("/revokeAccessToken") + public SaResult revokeAccessToken(String access_token) { + SaOAuth2Util.revokeAccessToken(access_token); + return SaResult.ok(); + } + + // 测试:回收全部 Access-Token --- http://localhost:8000/test/revokeAccessTokenByIndex?clientId=1001&loginId=10001 + @RequestMapping("/revokeAccessTokenByIndex") + public SaResult revokeAccessTokenByIndex(String clientId, long loginId) { + SaOAuth2Util.revokeAccessTokenByIndex(clientId, loginId); + return SaResult.ok(); + } + + // 测试:查询全部 Refresh-Token --- http://localhost:8000/test/getRefreshTokenValueList?clientId=1001&loginId=10001 + @RequestMapping("/getRefreshTokenValueList") + public SaResult getRefreshTokenValueList(String clientId, long loginId) { + List refreshTokenValueList = SaOAuth2Util.getRefreshTokenValueList(clientId, loginId); + return SaResult.data(refreshTokenValueList); + } + + // 测试:回收指定 Refresh-Token --- http://localhost:8000/test/revokeRefreshToken?refresh_token=xxxxxxxxxx + @RequestMapping("/revokeRefreshToken") + public SaResult revokeRefreshToken(String refresh_token) { + SaOAuth2Util.revokeRefreshToken(refresh_token); + return SaResult.ok(); + } + + // 测试:回收全部 Refresh-Token --- http://localhost:8000/test/revokeRefreshTokenByIndex?clientId=1001&loginId=10001 + @RequestMapping("/revokeRefreshTokenByIndex") + public SaResult revokeRefreshTokenByIndex(String clientId, long loginId) { + SaOAuth2Util.revokeRefreshTokenByIndex(clientId, loginId); + return SaResult.ok(); + } + + // 测试:查询全部 Client-Token --- http://localhost:8000/test/getClientTokenValueList?clientId=1001 + @RequestMapping("/getClientTokenValueList") + public SaResult getClientTokenValueList(String clientId) { + List clientTokenValueList = SaOAuth2Util.getClientTokenValueList(clientId); + return SaResult.data(clientTokenValueList); + } + + // 测试:回收指定 Client-Token --- http://localhost:8000/test/revokeClientToken?client_token=xxxxxxxxxxx + @RequestMapping("/revokeClientToken") + public SaResult revokeClientToken(String client_token) { + SaOAuth2Util.revokeClientToken(client_token); + return SaResult.ok(); + } + + // 测试:回收全部 Client-Token --- http://localhost:8000/test/revokeClientTokenByIndex?clientId=1001 + @RequestMapping("/revokeClientTokenByIndex") + public SaResult revokeClientTokenByIndex(String clientId) { + SaOAuth2Util.revokeClientTokenByIndex(clientId); + return SaResult.ok(); + } + +} \ No newline at end of file diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/SaOAuth2Manager.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/SaOAuth2Manager.java index 270da526..583d3ade 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/SaOAuth2Manager.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/SaOAuth2Manager.java @@ -17,7 +17,6 @@ package cn.dev33.satoken.oauth2; import cn.dev33.satoken.oauth2.config.SaOAuth2ServerConfig; import cn.dev33.satoken.oauth2.dao.SaOAuth2Dao; -import cn.dev33.satoken.oauth2.dao.SaOAuth2DaoDefaultImpl; import cn.dev33.satoken.oauth2.data.convert.SaOAuth2DataConverter; import cn.dev33.satoken.oauth2.data.convert.SaOAuth2DataConverterDefaultImpl; import cn.dev33.satoken.oauth2.data.generate.SaOAuth2DataGenerate; @@ -137,7 +136,7 @@ public class SaOAuth2Manager { if (dao == null) { synchronized (SaOAuth2Manager.class) { if (dao == null) { - setDao(new SaOAuth2DaoDefaultImpl()); + setDao(new SaOAuth2Dao()); } } } diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/config/SaOAuth2ServerConfig.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/config/SaOAuth2ServerConfig.java index 1bd95590..d5d5b455 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/config/SaOAuth2ServerConfig.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/config/SaOAuth2ServerConfig.java @@ -62,6 +62,15 @@ public class SaOAuth2ServerConfig implements Serializable { /** Lower-Client-Token 保存的时间(单位:秒) 默认为 -1,代表延续 Client-Token 有效期 */ public long lowerClientTokenTimeout = -1; + /** 全局默认配置所有应用:单个应用单个用户最多同时存在的 Access-Token 数量 */ + public int maxAccessTokenCount = 12; + + /** 全局默认配置所有应用:单个应用单个用户最多同时存在的 Refresh-Token 数量 */ + public int maxRefreshTokenCount = 12; + + /** 全局默认配置所有应用:单个应用最多同时存在的 Client-Token 数量 */ + public int maxClientTokenCount = 12; + /** 默认 openid 生成算法中使用的摘要前缀 */ public String openidDigestPrefix = SaOAuth2Consts.OPENID_DEFAULT_DIGEST_PREFIX; @@ -264,6 +273,58 @@ public class SaOAuth2ServerConfig implements Serializable { return this; } + /** + * @return maxAccessTokenCount + */ + public int getMaxAccessTokenCount() { + return maxAccessTokenCount; + } + + /** + * @param maxAccessTokenCount 要设置的 maxAccessTokenCount + * @return 对象自身 + */ + public SaOAuth2ServerConfig setMaxAccessTokenCount(int maxAccessTokenCount) { + this.maxAccessTokenCount = maxAccessTokenCount; + return this; + } + + /** + * 全局默认配置所有应用:单个应用单个用户最多同时存在的 Refresh-Token 数量 + * @return / + */ + public int getMaxRefreshTokenCount() { + return maxRefreshTokenCount; + } + + /** + * 全局默认配置所有应用:单个应用单个用户最多同时存在的 Refresh-Token 数量 + * @param maxRefreshTokenCount / + * @return 对象自身 + */ + public SaOAuth2ServerConfig setMaxRefreshTokenCount(int maxRefreshTokenCount) { + this.maxRefreshTokenCount = maxRefreshTokenCount; + return this; + } + + /** + * 全局默认配置所有应用:单个应用单个用户最多同时存在的 Client-Token 数量 + * @return / + */ + public int getMaxClientTokenCount() { + return maxClientTokenCount; + } + + /** + * 全局默认配置所有应用:单个应用单个用户最多同时存在的 Client-Token 数量 + * @param maxClientTokenCount / + * @return 对象自身 + */ + public SaOAuth2ServerConfig setMaxClientTokenCount(int maxClientTokenCount) { + this.maxClientTokenCount = maxClientTokenCount; + return this; + } + /** * @return openidDigestPrefix */ @@ -416,6 +477,9 @@ public class SaOAuth2ServerConfig implements Serializable { ", refreshTokenTimeout=" + refreshTokenTimeout + ", clientTokenTimeout=" + clientTokenTimeout + ", lowerClientTokenTimeout=" + lowerClientTokenTimeout + + ", maxAccessTokenCount=" + maxAccessTokenCount + + ", maxRefreshTokenCount=" + maxRefreshTokenCount + + ", maxClientTokenCount=" + maxClientTokenCount + ", openidDigestPrefix='" + openidDigestPrefix + ", unionidDigestPrefix='" + unionidDigestPrefix + ", higherScope='" + higherScope + diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/dao/SaOAuth2Dao.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/dao/SaOAuth2Dao.java index 815a3168..59b7e2e2 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/dao/SaOAuth2Dao.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/dao/SaOAuth2Dao.java @@ -18,15 +18,17 @@ package cn.dev33.satoken.oauth2.dao; import cn.dev33.satoken.SaManager; import cn.dev33.satoken.config.SaTokenConfig; import cn.dev33.satoken.dao.SaTokenDao; +import cn.dev33.satoken.fun.SaParamFunction; 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 cn.dev33.satoken.session.SaSession; +import cn.dev33.satoken.session.raw.SaRawSessionDelegator; import cn.dev33.satoken.util.SaFoxUtil; -import java.util.List; +import java.util.*; import static cn.dev33.satoken.oauth2.template.SaOAuth2Util.checkClientModel; @@ -36,308 +38,381 @@ import static cn.dev33.satoken.oauth2.template.SaOAuth2Util.checkClientModel; * @author click33 * @since 1.39.0 */ -public interface SaOAuth2Dao { +public class SaOAuth2Dao { - // ------------------- save 数据 + // ------------------- 索引操作公共代码 + + /** + * Raw Session 读写委托 + */ + public SaRawSessionDelegator oauth2RSD = new SaRawSessionDelegator("oauth2"); + + /** + * 在 raw-session 中的保存 Access-Token 索引列表使用的 key + */ + public static final String ACCESS_TOKEN_MAP = "__HD_ACCESS_TOKEN_MAP"; + + /** + * 在 raw-session 中的保存 Refresh-Token 索引列表使用的 key + */ + public static final String REFRESH_TOKEN_MAP = "__HD_REFRESH_TOKEN_MAP"; + + /** + * 在 raw-session 中的保存 Client-Token 索引列表使用的 key + */ + public static final String CLIENT_TOKEN_MAP = "__HD_CLIENT_TOKEN_MAP"; + + /** + * 获取 Access-Token 索引 RawSession + * @param clientId 应用 id + * @param loginId 账号 id + * @param isCreate 如果尚未创建,是否理解创建 + * @return / + */ + protected SaSession getRawSessionByAccessToken(String clientId, Object loginId, boolean isCreate) { + String value = splicingAccessTokenRSDValue(clientId, loginId); + return oauth2RSD.getSessionById(value, isCreate); + } + + /** + * 获取 refresh_token 索引 RawSession + * @param clientId 应用 id + * @param loginId 账号 id + * @param isCreate 如果尚未创建,是否理解创建 + * @return / + */ + protected SaSession getRawSessionByRefreshToken(String clientId, Object loginId, boolean isCreate) { + String value = splicingRefreshTokenRSDValue(clientId, loginId); + return oauth2RSD.getSessionById(value, isCreate); + } + + /** + * 获取 client_token 索引 RawSession + * @param clientId 应用 id + * @param isCreate 如果尚未创建,是否理解创建 + * @return / + */ + protected SaSession getRawSessionByClientToken(String clientId, boolean isCreate) { + String value = splicingClientTokenRSDValue(clientId); + return oauth2RSD.getSessionById(value, isCreate); + } + + /** + * 在 RawSession 上添加 token 索引 + * + * @param session 待操作的 RawSession + * @param tokenIndexMapSaveKey 在 session 上保存 token 索引列表使用的 key + * @param token 待添加的 token + * @param timeout 添加的 token 其过期时间 + * @param maxTokenCount 允许的最多 token 数量,超出的将被删除 (-1=不限制) + * @param removeFun 执行删除 token 的函数 + */ + protected void addTokenIndex(SaSession session, String tokenIndexMapSaveKey, String token, long timeout, int maxTokenCount, SaParamFunction removeFun) { + Map tokenIndexMap = session.get(tokenIndexMapSaveKey, this::newTokenIndexMap); + if(! tokenIndexMap.containsKey(token)) { + // 添加 + tokenIndexMap.put(token, ttlToExpireTime(timeout)); + // 剔除过期的 + tokenIndexMap = removeExpiredIndex(tokenIndexMap); + // 删掉溢出的 + tokenIndexMap = removeOverflowIndex(tokenIndexMap, maxTokenCount, removeFun); + // 保存 + session.set(tokenIndexMapSaveKey, tokenIndexMap); + // 更新 TTL + long maxTtl = getMaxTtl(tokenIndexMap.values()); + if(maxTtl != 0) { + session.updateTimeout(maxTtl); + } + } + } + + /** + * 在 RawSession 上删除 token 索引 + * @param session 待操作的 RawSession + * @param tokenIndexMapSaveKey 在 session 上保存 token 索引列表使用的 key + * @param token 待删除的 token + */ + protected void deleteTokenIndex(SaSession session, String tokenIndexMapSaveKey, String token) { + Map tokenIndexMap = session.get(tokenIndexMapSaveKey, this::newTokenIndexMap); + tokenIndexMap.remove(token); + // 如果删除后还有记录,就再次保存 + if( ! tokenIndexMap.isEmpty()) { + session.set(tokenIndexMapSaveKey, tokenIndexMap); + } else { + // 没有的话就直接注销此 RawSession + session.logout(); + } + } + + /** + * 剔除已过期的 token 索引 + * + * @param tokenIndexMap token 索引列表 + * @return 调整后的索引列表 + */ + protected Map removeExpiredIndex(Map tokenIndexMap) { + Map newTokenList = newTokenIndexMap(); + for (Map.Entry entry : tokenIndexMap.entrySet()) { + long ttl = expireTimeToTtl(entry.getValue()); + if(ttl != SaTokenDao.NOT_VALUE_EXPIRE) { + newTokenList.put(entry.getKey(), entry.getValue()); + } + } + return newTokenList; + } + + /** + * 将 token 索引列表中溢出的部分删除(按照插入顺序先进先出,不考虑每个剩余 token 剩余有效期) + * + * @param tokenIndexMap token 索引列表(key=token, value=token过期时间)(传入的 Map 必须是有序的) + * @param maxTokenCount 允许的最多 token 数量,超出的将被删除 (-1=不限制) + * @param removeFun 执行删除 token 的函数 + * @return 调整后的索引列表 + */ + protected Map removeOverflowIndex(Map tokenIndexMap, int maxTokenCount, SaParamFunction removeFun) { + + // 如果当前数量未超过限制,直接返回 + if (tokenIndexMap.size() <= maxTokenCount || maxTokenCount == SaTokenDao.NEVER_EXPIRE) { + return tokenIndexMap; + } + + // 创建新的索引 Map 副本 + Map newTokenIndexMap = newTokenIndexMap(); + + // 溢出数量 + int overflowCount = tokenIndexMap.size() - maxTokenCount; + + // 已删除 Token 数量 + int removedCount = 0; + + // 遍历原 Map 的所有条目 + for (Map.Entry entry : tokenIndexMap.entrySet()) { + String token = entry.getKey(); + if (removedCount < overflowCount) { + // 溢出部分:执行删除回调,但不添加到新 Map + removeFun.run(token); + removedCount++; + } else { + // 未溢出部分:添加到新 Map 副本 + newTokenIndexMap.put(token, entry.getValue()); + } + } + + // 返回索引 Map 副本 + return newTokenIndexMap; + } + + /** + * 获取 Token 列表 + * + * @param session 待操作的 RawSession + * @param tokenIndexMapSaveKey 在 session 上保存 token 索引列表使用的 key + * @return / + */ + protected List getTokenValueList(SaSession session, String tokenIndexMapSaveKey) { + if(session == null) { + return new ArrayList<>(); + } + + // 根据 ttl 值过滤一遍 + Map tokenIndexMap = session.get(tokenIndexMapSaveKey, this::newTokenIndexMap); + Map newTokenIndexMap = removeExpiredIndex(tokenIndexMap); + + // 如果调整后集合长度归零了,说明 token 已全部过期,直接注销此 RawSession + if(newTokenIndexMap.isEmpty()) { + session.logout(); + return new ArrayList<>(); + } + + // 没有归零,但是长度变小了,说明有过期的 token,需要重写写入一遍 + if(tokenIndexMap.size() > newTokenIndexMap.size()) { + session.set(tokenIndexMapSaveKey, newTokenIndexMap); + } + + // 转 List 返回 + return new ArrayList<>(newTokenIndexMap.keySet()); + } + + + // ------------------- code 操作 /** * 持久化:Code-Model * @param c . */ - default void saveCode(CodeModel c) { + public void saveCode(CodeModel c) { if(c == null) { return; } getSaTokenDao().setObject(splicingCodeSaveKey(c.code), c, SaOAuth2Manager.getServerConfig().getCodeTimeout()); } + /** + * 删除:Code + * @param code 值 + */ + public void deleteCode(String code) { + if(code != null) { + getSaTokenDao().deleteObject(splicingCodeSaveKey(code)); + } + } + + /** + * 获取:Code Model + * @param code . + * @return . + */ + public CodeModel getCode(String code) { + if(code == null) { + return null; + } + return (CodeModel)getSaTokenDao().getObject(splicingCodeSaveKey(code)); + } + + + // ------------------- code 索引 + /** * 持久化:Code-索引 * @param c . */ - default void saveCodeIndex(CodeModel c) { + public void saveCodeIndex(CodeModel c) { if(c == null) { return; } getSaTokenDao().set(splicingCodeIndexKey(c.clientId, c.loginId), c.code, SaOAuth2Manager.getServerConfig().getCodeTimeout()); } - /** - * 持久化:AccessToken-Model - * @param at . - */ - default void saveAccessToken(AccessTokenModel at) { - if(at == null) { - return; - } - getSaTokenDao().setObject(splicingAccessTokenSaveKey(at.accessToken), at, at.getExpiresIn()); - } - - /** - * 持久化:AccessToken-索引 - * @param at . - */ - default void saveAccessTokenIndex(AccessTokenModel at) { - if(at == null) { - return; - } - getSaTokenDao().set(splicingAccessTokenIndexKey(at.clientId, at.loginId), at.accessToken, at.getExpiresIn()); - } - - /** - * 持久化:RefreshToken-Model - * @param rt . - */ - default void saveRefreshToken(RefreshTokenModel rt) { - if(rt == null) { - return; - } - getSaTokenDao().setObject(splicingRefreshTokenSaveKey(rt.refreshToken), rt, rt.getExpiresIn()); - } - - /** - * 持久化:RefreshToken-索引 - * @param rt . - */ - default void saveRefreshTokenIndex(RefreshTokenModel rt) { - if(rt == null) { - return; - } - getSaTokenDao().set(splicingRefreshTokenIndexKey(rt.clientId, rt.loginId), rt.refreshToken, rt.getExpiresIn()); - } - - /** - * 持久化:ClientToken-Model - * @param ct . - */ - default void saveClientToken(ClientTokenModel ct) { - if(ct == null) { - return; - } - getSaTokenDao().setObject(splicingClientTokenSaveKey(ct.clientToken), ct, ct.getExpiresIn()); - } - - /** - * 持久化:ClientToken-索引 - * @param ct . - */ - default void saveClientTokenIndex(ClientTokenModel ct) { - if(ct == null) { - return; - } - getSaTokenDao().set(splicingClientTokenIndexKey(ct.clientId), ct.clientToken, ct.getExpiresIn()); - } - - /** - * 持久化:Lower-Client-Token 索引 - * @param ct / - */ - default void saveLowerClientTokenIndex(ClientTokenModel ct) { - if(ct == null) { - return; - } - long ttl = ct.getExpiresIn(); - // 如果此 client 单独配置了 Lower-Client-Token 的 TTL,则使用单独配置 - SaClientModel cm = checkClientModel(ct.clientId); - if (cm.getLowerClientTokenTimeout() != -1) { - ttl = cm.getLowerClientTokenTimeout(); - } - getSaTokenDao().set(splicingLowerClientTokenIndexKey(ct.clientId), ct.clientToken, ttl); - } - - /** - * 持久化:用户授权记录 - * @param clientId 应用id - * @param loginId 账号id - * @param scopes 权限列表 - */ - default void saveGrantScope(String clientId, Object loginId, List scopes) { - if( ! SaFoxUtil.isEmpty(scopes)) { - long ttl = checkClientModel(clientId).getAccessTokenTimeout(); - String value = SaOAuth2Manager.getDataConverter().convertScopeListToString(scopes); - getSaTokenDao().set(splicingGrantScopeKey(clientId, loginId), value, ttl); - } - } - - /** - * 持久化:state - * @param state / - */ - default void saveState(String state) { - if( ! SaFoxUtil.isEmpty(state)) { - long ttl = SaOAuth2Manager.getServerConfig().getCodeTimeout(); - getSaTokenDao().set(splicingStateSaveKey(state), state, ttl); - } - } - - /** - * 持久化:nonce-索引 - * @param c . - */ - default void saveCodeNonceIndex(CodeModel c) { - if(c == null || SaFoxUtil.isEmpty(c.nonce)) { - return; - } - getSaTokenDao().set(splicingCodeNonceIndexSaveKey(c.code), c.nonce, SaOAuth2Manager.getServerConfig().getCodeTimeout()); - } - - - // ------------------- delete数据 - - /** - * 删除:Code - * @param code 值 - */ - default void deleteCode(String code) { - if(code != null) { - getSaTokenDao().deleteObject(splicingCodeSaveKey(code)); - } - } - /** * 删除:Code索引 * @param clientId 应用id * @param loginId 账号id */ - default void deleteCodeIndex(String clientId, Object loginId) { + public void deleteCodeIndex(String clientId, Object loginId) { getSaTokenDao().delete(splicingCodeIndexKey(clientId, loginId)); } - /** - * 删除:Access-Token - * @param accessToken 值 - */ - default void deleteAccessToken(String accessToken) { - if(accessToken != null) { - getSaTokenDao().deleteObject(splicingAccessTokenSaveKey(accessToken)); - } - } - - /** - * 删除:Access-Token索引 - * @param clientId 应用id - * @param loginId 账号id - */ - default void deleteAccessTokenIndex(String clientId, Object loginId) { - getSaTokenDao().delete(splicingAccessTokenIndexKey(clientId, loginId)); - } - - /** - * 删除:Refresh-Token - * @param refreshToken 值 - */ - default void deleteRefreshToken(String refreshToken) { - if(refreshToken != null) { - getSaTokenDao().deleteObject(splicingRefreshTokenSaveKey(refreshToken)); - } - } - - /** - * 删除:Refresh-Token索引 - * @param clientId 应用id - * @param loginId 账号id - */ - default void deleteRefreshTokenIndex(String clientId, Object loginId) { - getSaTokenDao().delete(splicingRefreshTokenIndexKey(clientId, loginId)); - } - - /** - * 删除:Client-Token - * @param clientToken 值 - */ - default void deleteClientToken(String clientToken) { - if(clientToken != null) { - getSaTokenDao().deleteObject(splicingClientTokenSaveKey(clientToken)); - } - } - - /** - * 删除:Client-Token索引 - * @param clientId 应用id - */ - default void deleteClientTokenIndex(String clientId) { - getSaTokenDao().delete(splicingClientTokenIndexKey(clientId)); - } - - /** - * 删除:Lower-Client-Token - * @param lowerClientToken 值 - */ - default void deleteLowerClientToken(String lowerClientToken) { - // 其实就是删除 ClientToken - deleteClientToken(lowerClientToken); - } - - /** - * 删除:Lower-Client-Token索引 - * @param clientId 应用id - */ - default void deleteLowerClientTokenIndex(String clientId) { - getSaTokenDao().delete(splicingLowerClientTokenIndexKey(clientId)); - } - - /** - * 删除:用户授权记录 - * @param clientId 应用id - * @param loginId 账号id - */ - default void deleteGrantScope(String clientId, Object loginId) { - getSaTokenDao().delete(splicingGrantScopeKey(clientId, loginId)); - } - - /** - * 删除:state记录 - * @param state / - */ - default void deleteState(String state) { - getSaTokenDao().delete(splicingStateSaveKey(state)); - } - - - // ------------------- get 数据 - - /** - * 获取:Code Model - * @param code . - * @return . - */ - default CodeModel getCode(String code) { - if(code == null) { - return null; - } - return (CodeModel)getSaTokenDao().getObject(splicingCodeSaveKey(code)); - } - /** * 获取:Code Value * @param clientId 应用id * @param loginId 账号id * @return . */ - default String getCodeValue(String clientId, Object loginId) { + public String getCodeValue(String clientId, Object loginId) { return getSaTokenDao().get(splicingCodeIndexKey(clientId, loginId)); } + + // ------------------- access_token Model + + /** + * 持久化:AccessToken-Model + * @param at . + */ + public void saveAccessToken(AccessTokenModel at) { + if(at == null) { + return; + } + getSaTokenDao().setObject(splicingAccessTokenSaveKey(at.accessToken), at, at.getExpiresIn()); + } + + /** + * 删除:Access-Token + * @param accessToken 值 + */ + public void deleteAccessToken(String accessToken) { + if(accessToken != null) { + getSaTokenDao().deleteObject(splicingAccessTokenSaveKey(accessToken)); + } + } + /** * 获取:Access-Token Model * @param accessToken . * @return . */ - default AccessTokenModel getAccessToken(String accessToken) { + public AccessTokenModel getAccessToken(String accessToken) { if(accessToken == null) { return null; } return (AccessTokenModel)getSaTokenDao().getObject(splicingAccessTokenSaveKey(accessToken)); } + + // ------------------- access_token 索引 + /** - * 获取:Access-Token Value + * 持久化:Access-Token 索引 + * + * @param at / + * @param maxAccessTokenCount 允许的最多 Access-Token 数量,超出的将被删除 (-1=不限制) + */ + public void saveAccessTokenIndex(AccessTokenModel at, int maxAccessTokenCount) { + if(at == null) { + return; + } + SaSession session = getRawSessionByAccessToken(at.clientId, at.loginId, true); + addTokenIndex(session, ACCESS_TOKEN_MAP, at.accessToken, at.getExpiresIn(), maxAccessTokenCount, this::deleteAccessToken); + } + + /** + * 删除:Access-Token 在 RawSession 上的单个索引数据 + * + * @param clientId 应用 id + * @param loginId 账号id + * @param accessToken 值 + */ + public void deleteAccessTokenIndexBySingleData(String clientId, Object loginId, String accessToken) { + SaSession session = getRawSessionByAccessToken(clientId, loginId, false); + if(session == null) { + return; + } + deleteTokenIndex(session, ACCESS_TOKEN_MAP, accessToken); + } + + /** + * 删除:Access-Token 索引整体 * @param clientId 应用id * @param loginId 账号id - * @return . */ - default String getAccessTokenValue(String clientId, Object loginId) { - return getSaTokenDao().get(splicingAccessTokenIndexKey(clientId, loginId)); + public void deleteAccessTokenIndex(String clientId, Object loginId) { + oauth2RSD.deleteSessionById(splicingAccessTokenRSDValue(clientId, loginId)); + } + + /** + * 获取 Access-Token 列表:此应用下 对 某个用户 签发的所有 Access-token + * + * @param clientId 应用id + * @param loginId 账号id + * @return / + */ + public List getAccessTokenValueList(String clientId, Object loginId) { + SaSession session = getRawSessionByAccessToken(clientId, loginId, false); + return getTokenValueList(session, ACCESS_TOKEN_MAP); + } + + + // ------------------- refresh_token Model + + /** + * 持久化:RefreshToken-Model + * @param rt . + */ + public void saveRefreshToken(RefreshTokenModel rt) { + if(rt == null) { + return; + } + getSaTokenDao().setObject(splicingRefreshTokenSaveKey(rt.refreshToken), rt, rt.getExpiresIn()); + } + + /** + * 删除:Refresh-Token + * @param refreshToken 值 + */ + public void deleteRefreshToken(String refreshToken) { + if(refreshToken != null) { + getSaTokenDao().deleteObject(splicingRefreshTokenSaveKey(refreshToken)); + } } /** @@ -345,21 +420,88 @@ public interface SaOAuth2Dao { * @param refreshToken . * @return . */ - default RefreshTokenModel getRefreshToken(String refreshToken) { + public RefreshTokenModel getRefreshToken(String refreshToken) { if(refreshToken == null) { return null; } return (RefreshTokenModel)getSaTokenDao().getObject(splicingRefreshTokenSaveKey(refreshToken)); } + + // ------------------- refresh_token 索引 + /** - * 获取:Refresh-Token Value + * 持久化:Refresh-Token 索引 + * + * @param rt / + * @param maxRefreshTokenCount 允许的最多 Refresh-Token 数量,超出的将被删除 (-1=不限制) + */ + public void saveRefreshTokenIndex(RefreshTokenModel rt, int maxRefreshTokenCount) { + if(rt == null) { + return; + } + SaSession session = getRawSessionByRefreshToken(rt.clientId, rt.loginId, true); + addTokenIndex(session, REFRESH_TOKEN_MAP, rt.refreshToken, rt.getExpiresIn(), maxRefreshTokenCount, this::deleteRefreshToken); + } + + /** + * 删除:Refresh-Token 在 RawSession 上的单个索引数据 + * + * @param clientId 应用 id + * @param loginId 账号id + * @param refreshToken 值 + */ + public void deleteRefreshTokenIndexBySingleData(String clientId, Object loginId, String refreshToken) { + SaSession session = getRawSessionByRefreshToken(clientId, loginId, false); + if(session == null) { + return; + } + deleteTokenIndex(session, REFRESH_TOKEN_MAP, refreshToken); + } + + /** + * 删除:Refresh-Token 索引整体 * @param clientId 应用id * @param loginId 账号id - * @return . */ - default String getRefreshTokenValue(String clientId, Object loginId) { - return getSaTokenDao().get(splicingRefreshTokenIndexKey(clientId, loginId)); + public void deleteRefreshTokenIndex(String clientId, Object loginId) { + oauth2RSD.deleteSessionById(splicingRefreshTokenRSDValue(clientId, loginId)); + } + + /** + * 获取 Refresh-Token 列表:此应用下 对 某个用户 签发的所有 Refresh-token + * + * @param clientId 应用id + * @param loginId 账号id + * @return / + */ + public List getRefreshTokenValueList(String clientId, Object loginId) { + SaSession session = getRawSessionByRefreshToken(clientId, loginId, false); + return getTokenValueList(session, REFRESH_TOKEN_MAP); + } + + + // ------------------- client_token Model + + /** + * 持久化:ClientToken-Model + * @param ct . + */ + public void saveClientToken(ClientTokenModel ct) { + if(ct == null) { + return; + } + getSaTokenDao().setObject(splicingClientTokenSaveKey(ct.clientToken), ct, ct.getExpiresIn()); + } + + /** + * 删除:Client-Token + * @param clientToken 值 + */ + public void deleteClientToken(String clientToken) { + if(clientToken != null) { + getSaTokenDao().deleteObject(splicingClientTokenSaveKey(clientToken)); + } } /** @@ -367,29 +509,87 @@ public interface SaOAuth2Dao { * @param clientToken . * @return . */ - default ClientTokenModel getClientToken(String clientToken) { + public ClientTokenModel getClientToken(String clientToken) { if(clientToken == null) { return null; } return (ClientTokenModel)getSaTokenDao().getObject(splicingClientTokenSaveKey(clientToken)); } + + // ------------------- client_token 索引 + /** - * 获取:Client-Token Value - * @param clientId 应用id - * @return . + * 持久化:Client-Token 索引 + * + * @param ct / + * @param maxClientTokenCount 允许的最多 Client-Token 数量,超出的将被删除 (-1=不限制) */ - default String getClientTokenValue(String clientId) { - return getSaTokenDao().get(splicingClientTokenIndexKey(clientId)); + public void saveClientTokenIndex(ClientTokenModel ct, int maxClientTokenCount) { + if(ct == null) { + return; + } + SaSession session = getRawSessionByClientToken(ct.clientId, true); + addTokenIndex(session, CLIENT_TOKEN_MAP, ct.clientToken, ct.getExpiresIn(), maxClientTokenCount, this::deleteClientToken); } /** - * 获取:Lower-Client-Token Value - * @param clientId 应用id - * @return . + * 删除:Client-Token 在 RawSession 上的单个索引数据 + * @param clientId 应用 id + * @param clientToken 值 */ - default String getLowerClientTokenValue(String clientId) { - return getSaTokenDao().get(splicingLowerClientTokenIndexKey(clientId)); + public void deleteClientTokenIndexBySingleData(String clientId, String clientToken) { + SaSession session = getRawSessionByClientToken(clientId, false); + if(session == null) { + return; + } + deleteTokenIndex(session, CLIENT_TOKEN_MAP, clientToken); + } + + /** + * 删除:Client-Token 索引整体 + * + * @param clientId 应用id + */ + public void deleteClientTokenIndex(String clientId) { + oauth2RSD.deleteSessionById(splicingClientTokenRSDValue(clientId)); + } + + /** + * 获取 Client-Token 列表:此应用下 对 某个用户 签发的所有 Client-token + * + * @param clientId 应用id + * @return / + */ + public List getClientTokenValueList(String clientId) { + SaSession session = getRawSessionByClientToken(clientId, false); + return getTokenValueList(session, CLIENT_TOKEN_MAP); + } + + + // ------------------- GrantScope + + /** + * 持久化:用户授权记录 + * @param clientId 应用id + * @param loginId 账号id + * @param scopes 权限列表 + */ + public void saveGrantScope(String clientId, Object loginId, List scopes) { + if( ! SaFoxUtil.isEmpty(scopes)) { + long ttl = checkClientModel(clientId).getAccessTokenTimeout(); + String value = SaOAuth2Manager.getDataConverter().convertScopeListToString(scopes); + getSaTokenDao().set(splicingGrantScopeKey(clientId, loginId), value, ttl); + } + } + + /** + * 删除:用户授权记录 + * @param clientId 应用id + * @param loginId 账号id + */ + public void deleteGrantScope(String clientId, Object loginId) { + getSaTokenDao().delete(splicingGrantScopeKey(clientId, loginId)); } /** @@ -398,29 +598,65 @@ public interface SaOAuth2Dao { * @param loginId 账号id * @return 权限 */ - default List getGrantScope(String clientId, Object loginId) { + public List getGrantScope(String clientId, Object loginId) { String value = getSaTokenDao().get(splicingGrantScopeKey(clientId, loginId)); return SaOAuth2Manager.getDataConverter().convertScopeStringToList(value); } + + // ------------------- State + + /** + * 持久化:state + * @param state / + */ + public void saveState(String state) { + if( ! SaFoxUtil.isEmpty(state)) { + long ttl = SaOAuth2Manager.getServerConfig().getCodeTimeout(); + getSaTokenDao().set(splicingStateSaveKey(state), state, ttl); + } + } + + /** + * 删除:state记录 + * @param state / + */ + public void deleteState(String state) { + getSaTokenDao().delete(splicingStateSaveKey(state)); + } + /** * 获取:state * @param state / * @return / */ - default String getState(String state) { + public String getState(String state) { if(SaFoxUtil.isEmpty(state)) { return null; } return getSaTokenDao().get(splicingStateSaveKey(state)); } + + // ------------------- 其它 + + /** + * 持久化:nonce-索引 + * @param c . + */ + public void saveCodeNonceIndex(CodeModel c) { + if(c == null || SaFoxUtil.isEmpty(c.nonce)) { + return; + } + getSaTokenDao().set(splicingCodeNonceIndexSaveKey(c.code), c.nonce, SaOAuth2Manager.getServerConfig().getCodeTimeout()); + } + /** * 获取:nonce * @param code / * @return / */ - default String getNonce(String code) { + public String getNonce(String code) { if(SaFoxUtil.isEmpty(code)) { return null; } @@ -435,17 +671,17 @@ public interface SaOAuth2Dao { * @param code 授权码 * @return key */ - default String splicingCodeSaveKey(String code) { + public String splicingCodeSaveKey(String code) { return getSaTokenConfig().getTokenName() + ":oauth2:code:" + code; } /** - * 拼接key:Code索引 + * 拼接key:Code 索引 * @param clientId 应用id * @param loginId 账号id * @return key */ - default String splicingCodeIndexKey(String clientId, Object loginId) { + public String splicingCodeIndexKey(String clientId, Object loginId) { return getSaTokenConfig().getTokenName() + ":oauth2:code-index:" + clientId + ":" + loginId; } @@ -454,18 +690,37 @@ public interface SaOAuth2Dao { * @param accessToken accessToken * @return key */ - default String splicingAccessTokenSaveKey(String accessToken) { + public String splicingAccessTokenSaveKey(String accessToken) { return getSaTokenConfig().getTokenName() + ":oauth2:access-token:" + accessToken; } /** - * 拼接key:Access-Token索引 + * 拼接key:Access-Token RSD Value * @param clientId 应用id * @param loginId 账号id * @return key */ - default String splicingAccessTokenIndexKey(String clientId, Object loginId) { - return getSaTokenConfig().getTokenName() + ":oauth2:access-token-index:" + clientId + ":" + loginId; + public String splicingAccessTokenRSDValue(String clientId, Object loginId) { + return "access-token:" + clientId + ":" + loginId; + } + + /** + * 拼接key:Refresh-Token RSD Value + * @param clientId 应用id + * @param loginId 账号id + * @return key + */ + public String splicingRefreshTokenRSDValue(String clientId, Object loginId) { + return "refresh-token:" + clientId + ":" + loginId; + } + + /** + * 拼接key:Client-Token RSD Value + * @param clientId 应用id + * @return key + */ + public String splicingClientTokenRSDValue(String clientId) { + return "client-token:" + clientId; } /** @@ -473,54 +728,26 @@ public interface SaOAuth2Dao { * @param refreshToken refreshToken * @return key */ - default String splicingRefreshTokenSaveKey(String refreshToken) { + public String splicingRefreshTokenSaveKey(String refreshToken) { return getSaTokenConfig().getTokenName() + ":oauth2:refresh-token:" + refreshToken; } - /** - * 拼接key:Refresh-Token索引 - * @param clientId 应用id - * @param loginId 账号id - * @return key - */ - default String splicingRefreshTokenIndexKey(String clientId, Object loginId) { - return getSaTokenConfig().getTokenName() + ":oauth2:refresh-token-index:" + clientId + ":" + loginId; - } - /** * 拼接key:Client-Token持久化 * @param clientToken clientToken * @return key */ - default String splicingClientTokenSaveKey(String clientToken) { + public String splicingClientTokenSaveKey(String clientToken) { return getSaTokenConfig().getTokenName() + ":oauth2:client-token:" + clientToken; } - /** - * 拼接key:Client-Token 索引 - * @param clientId clientId - * @return key - */ - default String splicingClientTokenIndexKey(String clientId) { - return getSaTokenConfig().getTokenName() + ":oauth2:client-token-index:" + clientId; - } - - /** - * 拼接key:Lower-Client-Token 索引 - * @param clientId clientId - * @return key - */ - default String splicingLowerClientTokenIndexKey(String clientId) { - return getSaTokenConfig().getTokenName() + ":oauth2:lower-client-token-index:" + clientId; - } - /** * 拼接key:用户授权记录 * @param clientId 应用id * @param loginId 账号id * @return key */ - default String splicingGrantScopeKey(String clientId, Object loginId) { + public String splicingGrantScopeKey(String clientId, Object loginId) { return getSaTokenConfig().getTokenName() + ":oauth2:grant-scope:" + clientId + ":" + loginId; } @@ -529,7 +756,7 @@ public interface SaOAuth2Dao { * @param state / * @return key */ - default String splicingStateSaveKey(String state) { + public String splicingStateSaveKey(String state) { return getSaTokenConfig().getTokenName() + ":oauth2:state:" + state; } @@ -538,11 +765,78 @@ public interface SaOAuth2Dao { * @param code 授权码 * @return key */ - default String splicingCodeNonceIndexSaveKey(String code) { + public String splicingCodeNonceIndexSaveKey(String code) { return getSaTokenConfig().getTokenName() + ":oauth2:code-nonce-index:" + code; } + // -------- 工具方法 + + /** + * 获取一个新的 TokenMap 集合 + * @return / + */ + protected Map newTokenIndexMap() { + return new LinkedHashMap<>(); + } + + /** + * 过期时间转 ttl (秒) 获取最大 ttl 值 + * @param expireTimeList / + * @return / + */ + protected long getMaxTtl(Collection expireTimeList) { + long maxTtl = 0; + for (long expireTime : expireTimeList) { + long ttl = expireTimeToTtl(expireTime); + if(ttl == SaTokenDao.NEVER_EXPIRE) { + maxTtl = SaTokenDao.NEVER_EXPIRE; + break; + } + if(ttl > maxTtl) { + maxTtl = ttl; + } + } + return maxTtl; + } + + /** + * 过期时间转 ttl (秒) + * @param expireTime / + * @return / + */ + protected long expireTimeToTtl(long expireTime) { + if(expireTime == SaTokenDao.NEVER_EXPIRE) { + return SaTokenDao.NEVER_EXPIRE; + } + if(expireTime == SaTokenDao.NOT_VALUE_EXPIRE) { + return SaTokenDao.NOT_VALUE_EXPIRE; + } + // TODO temp-token 模块与 apikey 模块是否也应该修改为这个逻辑 ? + long currentTime = System.currentTimeMillis(); + if(expireTime < currentTime) { + return SaTokenDao.NOT_VALUE_EXPIRE; + } + return (expireTime - currentTime) / 1000; + } + + /** + * ttl (秒) 转 过期时间 + * @param ttl / + * @return / + */ + protected long ttlToExpireTime(long ttl) { + if(ttl == SaTokenDao.NEVER_EXPIRE) { + return SaTokenDao.NEVER_EXPIRE; + } + if(ttl == SaTokenDao.NOT_VALUE_EXPIRE) { + return SaTokenDao.NOT_VALUE_EXPIRE; + } + return ttl * 1000 + System.currentTimeMillis(); + } + + + // -------- bean 对象代理 /** @@ -550,7 +844,7 @@ public interface SaOAuth2Dao { * * @return / */ - default SaTokenDao getSaTokenDao() { + public SaTokenDao getSaTokenDao() { return SaManager.getSaTokenDao(); } @@ -559,7 +853,7 @@ public interface SaOAuth2Dao { * * @return / */ - default SaTokenConfig getSaTokenConfig() { + public SaTokenConfig getSaTokenConfig() { return SaManager.getConfig(); } diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/dao/SaOAuth2DaoDefaultImpl.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/dao/SaOAuth2DaoDefaultImpl.java deleted file mode 100644 index 0ebd09f2..00000000 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/dao/SaOAuth2DaoDefaultImpl.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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.dao; - -/** - * Sa-Token OAuth2 数据持久层,默认实现 - * - * @author click33 - * @since 1.39.0 - */ -public class SaOAuth2DaoDefaultImpl implements SaOAuth2Dao { - -} 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 ee40ca58..cdac1eb4 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 @@ -39,7 +39,7 @@ public interface SaOAuth2DataGenerate { CodeModel generateCode(RequestAuthModel ra); /** - * 构建Model:Access-Token + * 构建Model:Access-Token (根据 code 授权码) * @param code 授权码Model * @return AccessToken Model */ 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 7f535b1f..824165d7 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 @@ -74,7 +74,7 @@ public class SaOAuth2DataGenerateDefaultImpl implements SaOAuth2DataGenerate { } /** - * 构建Model:Access-Token + * 构建Model:Access-Token (根据 code 授权码) * @param code 授权码 * @return AccessToken Model */ @@ -88,9 +88,9 @@ public class SaOAuth2DataGenerateDefaultImpl implements SaOAuth2DataGenerate { CodeModel cm = dao.getCode(code); SaOAuth2AuthorizationCodeException.throwBy(cm == null, "无效 code: " + code, code, SaOAuth2ErrorCode.CODE_30110); - // 2、删除旧Token - dao.deleteAccessToken(dao.getAccessTokenValue(cm.clientId, cm.loginId)); - dao.deleteRefreshToken(dao.getRefreshTokenValue(cm.clientId, cm.loginId)); + // 2、删除旧Token,TODO 目测不用删,保存索引的时候如果超出了会自动删 +// dao.deleteAccessToken(dao.getAccessTokenList(cm.clientId, cm.loginId)); +// dao.deleteRefreshToken(dao.getRefreshTokenValue(cm.clientId, cm.loginId)); // 3、生成token AccessTokenModel at = dataConverter.convertCodeToAccessToken(cm); @@ -100,10 +100,11 @@ public class SaOAuth2DataGenerateDefaultImpl implements SaOAuth2DataGenerate { at.refreshExpiresTime = rt.expiresTime; // 4、保存token + SaClientModel clientModel = SaOAuth2Manager.getDataLoader().getClientModelNotNull(cm.clientId); dao.saveAccessToken(at); - dao.saveAccessTokenIndex(at); + dao.saveAccessTokenIndex(at, clientModel.getMaxAccessTokenCount()); dao.saveRefreshToken(rt); - dao.saveRefreshTokenIndex(rt); + dao.saveRefreshTokenIndex(rt, clientModel.getMaxRefreshTokenCount()); // 5、删除此Code dao.deleteCode(code); @@ -131,16 +132,16 @@ public class SaOAuth2DataGenerateDefaultImpl implements SaOAuth2DataGenerate { SaClientModel clientModel = SaOAuth2Manager.getDataLoader().getClientModelNotNull(rt.clientId); if(clientModel.getIsNewRefresh()) { // 删除旧 Refresh-Token - dao.deleteRefreshToken(rt.refreshToken); +// dao.deleteRefreshToken(rt.refreshToken); // 创建并保存新的 Refresh-Token rt = SaOAuth2Manager.getDataConverter().convertRefreshTokenToRefreshToken(rt); dao.saveRefreshToken(rt); - dao.saveRefreshTokenIndex(rt); + dao.saveRefreshTokenIndex(rt, clientModel.getMaxRefreshTokenCount()); } // 删除旧 Access-Token - dao.deleteAccessToken(dao.getAccessTokenValue(rt.clientId, rt.loginId)); +// dao.deleteAccessToken(dao.getAccessTokenList(rt.clientId, rt.loginId)); // 生成新 Access-Token AccessTokenModel at = SaOAuth2Manager.getDataConverter().convertRefreshTokenToAccessToken(rt); @@ -148,7 +149,7 @@ public class SaOAuth2DataGenerateDefaultImpl implements SaOAuth2DataGenerate { // 保存新 Access-Token dao.saveAccessToken(at); - dao.saveAccessTokenIndex(at); + dao.saveAccessTokenIndex(at, clientModel.getMaxAccessTokenCount()); // 返回新 Access-Token return at; @@ -168,10 +169,10 @@ public class SaOAuth2DataGenerateDefaultImpl implements SaOAuth2DataGenerate { SaOAuth2Dao dao = SaOAuth2Manager.getDao(); // 1、删除 旧Token - dao.deleteAccessToken(dao.getAccessTokenValue(ra.clientId, ra.loginId)); - if(isCreateRt) { - dao.deleteRefreshToken(dao.getRefreshTokenValue(ra.clientId, ra.loginId)); - } +// dao.deleteAccessToken(dao.getAccessTokenList(ra.clientId, ra.loginId)); +// if(isCreateRt) { +// dao.deleteRefreshToken(dao.getRefreshTokenValue(ra.clientId, ra.loginId)); +// } // 2、生成 新Access-Token String newAtValue = SaOAuth2Strategy.instance.createAccessToken.execute(ra.clientId, ra.loginId, ra.scopes); @@ -195,12 +196,12 @@ public class SaOAuth2DataGenerateDefaultImpl implements SaOAuth2DataGenerate { at.refreshExpiresTime = rt.expiresTime; dao.saveRefreshToken(rt); - dao.saveRefreshTokenIndex(rt); + dao.saveRefreshTokenIndex(rt, clientModel.getMaxRefreshTokenCount()); } // 5、保存 新Access-Token dao.saveAccessToken(at); - dao.saveAccessTokenIndex(at); + dao.saveAccessTokenIndex(at, clientModel.getMaxAccessTokenCount()); // 6、返回 新Access-Token return at; @@ -218,18 +219,18 @@ public class SaOAuth2DataGenerateDefaultImpl implements SaOAuth2DataGenerate { SaOAuth2Dao dao = SaOAuth2Manager.getDao(); // 1、删掉旧 Lower-Client-Token - dao.deleteClientToken(dao.getLowerClientTokenValue(clientId)); +// dao.deleteClientToken(dao.getLowerClientTokenValue(clientId)); // 2、将旧Client-Token 标记为新 Lower-Client-Token - ClientTokenModel oldCt = dao.getClientToken(dao.getClientTokenValue(clientId)); - dao.saveLowerClientTokenIndex(oldCt); +// ClientTokenModel oldCt = dao.getClientToken(dao.getClientTokenValue(clientId)); +// dao.saveLowerClientTokenIndex(oldCt); // 2.5、如果配置了 Lower-Client-Token 的 ttl ,则需要更新一下 SaClientModel cm = SaOAuth2Manager.getDataLoader().getClientModelNotNull(clientId); - if(oldCt != null && cm.getLowerClientTokenTimeout() != -1) { - oldCt.expiresTime = System.currentTimeMillis() + (cm.getLowerClientTokenTimeout() * 1000); - dao.saveClientToken(oldCt); - } +// if(oldCt != null && cm.getLowerClientTokenTimeout() != -1) { +// oldCt.expiresTime = System.currentTimeMillis() + (cm.getLowerClientTokenTimeout() * 1000); +// dao.saveClientToken(oldCt); +// } // 3、生成新 Client-Token String clientTokenValue = SaOAuth2Strategy.instance.createClientToken.execute(clientId, scopes); @@ -242,7 +243,7 @@ public class SaOAuth2DataGenerateDefaultImpl implements SaOAuth2DataGenerate { // 3、保存新Client-Token dao.saveClientToken(ct); - dao.saveClientTokenIndex(ct); + dao.saveClientTokenIndex(ct, cm.getMaxClientTokenCount()); // 4、返回 return ct; diff --git a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/model/loader/SaClientModel.java b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/model/loader/SaClientModel.java index 31778c59..54b06f9d 100644 --- a/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/model/loader/SaClientModel.java +++ b/sa-token-plugin/sa-token-oauth2/src/main/java/cn/dev33/satoken/oauth2/data/model/loader/SaClientModel.java @@ -81,6 +81,15 @@ public class SaClientModel implements Serializable { /** 是否允许此应用自动确认授权(高危配置,禁止向不被信任的第三方开启此选项) */ public Boolean isAutoConfirm = false; + /** 此应用单个用户最多同时存在的 Access-Token 数量 */ + public int maxAccessTokenCount; + + /** 此应用单个用户最多同时存在的 Refresh-Token 数量 */ + public int maxRefreshTokenCount; + + /** 此应用最多同时存在的 Client-Token 数量 */ + public int maxClientTokenCount; + public SaClientModel() { SaOAuth2ServerConfig config = SaOAuth2Manager.getServerConfig(); @@ -89,6 +98,9 @@ public class SaClientModel implements Serializable { this.refreshTokenTimeout = config.getRefreshTokenTimeout(); this.clientTokenTimeout = config.getClientTokenTimeout(); this.lowerClientTokenTimeout = config.getLowerClientTokenTimeout(); + this.maxAccessTokenCount = config.getMaxAccessTokenCount(); + this.maxRefreshTokenCount = config.getMaxRefreshTokenCount(); + this.maxClientTokenCount = config.getMaxClientTokenCount(); } public SaClientModel(String clientId, String clientSecret, List contractScopes, List allowRedirectUris) { this(); @@ -98,6 +110,48 @@ public class SaClientModel implements Serializable { this.allowRedirectUris = allowRedirectUris; } + + // 追加方法 + + /** + * @param scopes 添加应用签约的所有权限 + * @return 对象自身 + */ + public SaClientModel addContractScopes(String... scopes) { + if(this.contractScopes == null) { + this.contractScopes = new ArrayList<>(); + } + this.contractScopes.addAll(Arrays.asList(scopes)); + return this; + } + + /** + * @param redirectUris 添加应用允许授权的所有 redirect_uri + * @return 对象自身 + */ + public SaClientModel addAllowRedirectUris(String... redirectUris) { + if(this.allowRedirectUris == null) { + this.allowRedirectUris = new ArrayList<>(); + } + this.allowRedirectUris.addAll(Arrays.asList(redirectUris)); + return this; + } + + /** + * @param grantTypes 应用允许的所有 grant_type + * @return 对象自身 + */ + public SaClientModel addAllowGrantTypes(String... grantTypes) { + if(this.allowGrantTypes == null) { + this.allowGrantTypes = new ArrayList<>(); + } + this.allowGrantTypes.addAll(Arrays.asList(grantTypes)); + return this; + } + + + // get set + /** * @return 应用id */ @@ -297,7 +351,60 @@ public class SaClientModel implements Serializable { this.isAutoConfirm = isAutoConfirm; return this; } - // + + /** + * 此应用单个用户最多同时存在的 Access-Token 数量 + * @return / + */ + public int getMaxAccessTokenCount() { + return maxAccessTokenCount; + } + + /** + * 设置 此应用单个用户最多同时存在的 Access-Token 数量 + * @param maxAccessTokenCount / + * @return 对象自身 + */ + public SaClientModel setMaxAccessTokenCount(int maxAccessTokenCount) { + this.maxAccessTokenCount = maxAccessTokenCount; + return this; + } + + /** + * 此应用单个用户最多同时存在的 Refresh-Token 数量 + * @return / + */ + public int getMaxRefreshTokenCount() { + return maxRefreshTokenCount; + } + + /** + * 此应用单个用户最多同时存在的 Refresh-Token 数量 + * @param maxRefreshTokenCount / + * @return 对象自身 + */ + public SaClientModel setMaxRefreshTokenCount(int maxRefreshTokenCount) { + this.maxRefreshTokenCount = maxRefreshTokenCount; + return this; + } + + /** + * 此应用单个用户最多同时存在的 Client-Token 数量 + * @return / + */ + public int getMaxClientTokenCount() { + return maxClientTokenCount; + } + + /** + * 此应用单个用户最多同时存在的 Client-Token 数量 + * @param maxClientTokenCount / + * @return 对象自身 + */ + public SaClientModel setMaxClientTokenCount(int maxClientTokenCount) { + this.maxClientTokenCount = maxClientTokenCount; + return this; + } @Override public String toString() { @@ -314,47 +421,10 @@ public class SaClientModel implements Serializable { ", clientTokenTimeout=" + clientTokenTimeout + ", lowerClientTokenTimeout=" + lowerClientTokenTimeout + ", isAutoConfirm=" + isAutoConfirm + + ", maxAccessTokenCount=" + maxAccessTokenCount + + ", refreshTokenTimeout=" + refreshTokenTimeout + + ", maxClientTokenCount=" + maxClientTokenCount + '}'; } - - // 追加方法 - - /** - * @param scopes 添加应用签约的所有权限 - * @return 对象自身 - */ - public SaClientModel addContractScopes(String... scopes) { - if(this.contractScopes == null) { - this.contractScopes = new ArrayList<>(); - } - this.contractScopes.addAll(Arrays.asList(scopes)); - return this; - } - - /** - * @param redirectUris 添加应用允许授权的所有 redirect_uri - * @return 对象自身 - */ - public SaClientModel addAllowRedirectUris(String... redirectUris) { - if(this.allowRedirectUris == null) { - this.allowRedirectUris = new ArrayList<>(); - } - this.allowRedirectUris.addAll(Arrays.asList(redirectUris)); - return this; - } - - /** - * @param grantTypes 应用允许的所有 grant_type - * @return 对象自身 - */ - public SaClientModel addAllowGrantTypes(String... grantTypes) { - if(this.allowGrantTypes == null) { - this.allowGrantTypes = new ArrayList<>(); - } - this.allowGrantTypes.addAll(Arrays.asList(grantTypes)); - return this; - } - - } 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 40a33246..31891201 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 @@ -428,13 +428,14 @@ public class SaOAuth2Template { } /** - * 获取 Access-Token,根据索引: clientId、loginId + * 获取 Access-Token 列表:此应用下 对 某个用户 签发的所有 Access-token + * * @param clientId / * @param loginId / * @return / */ - public String getAccessTokenValue(String clientId, Object loginId) { - return SaOAuth2Manager.getDao().getAccessTokenValue(clientId, loginId); + public List getAccessTokenValueList(String clientId, Object loginId) { + return SaOAuth2Manager.getDao().getAccessTokenValueList(clientId, loginId); } /** @@ -490,7 +491,7 @@ public class SaOAuth2Template { } /** - * 回收 Access-Token + * 回收一个 Access-Token * @param accessToken Access-Token值 */ public void revokeAccessToken(String accessToken) { @@ -502,21 +503,24 @@ public class SaOAuth2Template { // 删 at、索引 SaOAuth2Dao dao = SaOAuth2Manager.getDao(); dao.deleteAccessToken(accessToken); - dao.deleteAccessTokenIndex(at.clientId, at.loginId); + dao.deleteAccessTokenIndexBySingleData(at.clientId, at.loginId, accessToken); } /** - * 回收 Access-Token,根据索引: clientId、loginId + * 回收全部 Access-Token:指定应用下 指定用户 的全部 Access-Token * @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); + List accessTokenList = getAccessTokenValueList(clientId, loginId); + if( ! accessTokenList.isEmpty()) { + // 删 AT + for (String accessToken : accessTokenList) { + dao.deleteAccessToken(accessToken); + } + // 删索引 dao.deleteAccessTokenIndex(clientId, loginId); } } @@ -549,17 +553,19 @@ public class SaOAuth2Template { } /** - * 获取 Refresh-Token,根据索引: clientId、loginId + * 获取 Refresh-Token 列表:此应用下 对 某个用户 签发的所有 Refresh-Token + * * @param clientId / * @param loginId / * @return / */ - public String getRefreshTokenValue(String clientId, Object loginId) { - return SaOAuth2Manager.getDao().getRefreshTokenValue(clientId, loginId); + public List getRefreshTokenValueList(String clientId, Object loginId) { + return SaOAuth2Manager.getDao().getRefreshTokenValueList(clientId, loginId); } /** - * 回收 Refresh-Token + * 回收一个 Refresh-Token + * * @param refreshToken Refresh-Token 值 */ public void revokeRefreshToken(String refreshToken) { @@ -571,21 +577,25 @@ public class SaOAuth2Template { // 删 rt、索引 SaOAuth2Dao dao = SaOAuth2Manager.getDao(); dao.deleteRefreshToken(refreshToken); - dao.deleteRefreshTokenIndex(rt.clientId, rt.loginId); + dao.deleteRefreshTokenIndexBySingleData(rt.clientId, rt.loginId, refreshToken); } /** - * 回收 Refresh-Token,根据索引: clientId、loginId + * 回收全部 Refresh-Token:指定应用下 指定用户 的全部 Refresh-Token + * * @param clientId / * @param loginId / */ public void revokeRefreshTokenByIndex(String clientId, Object loginId) { SaOAuth2Dao dao = SaOAuth2Manager.getDao(); - // 删 rt、删索引 - String refreshToken = getRefreshTokenValue(clientId, loginId); - if(refreshToken != null) { - dao.deleteRefreshToken(refreshToken); + List refreshTokenList = getRefreshTokenValueList(clientId, loginId); + if( ! refreshTokenList.isEmpty()) { + // 删 RT + for (String refreshToken : refreshTokenList) { + dao.deleteRefreshToken(refreshToken); + } + // 删索引 dao.deleteRefreshTokenIndex(clientId, loginId); } } @@ -627,12 +637,13 @@ public class SaOAuth2Template { } /** - * 获取 ClientToken,根据索引: clientId + * 获取 Client-Token 列表:此应用下 对 某个用户 签发的所有 Client-token + * * @param clientId / * @return / */ - public String getClientTokenValue(String clientId) { - return SaOAuth2Manager.getDao().getClientTokenValue(clientId); + public List getClientTokenValueList(String clientId) { + return SaOAuth2Manager.getDao().getClientTokenValueList(clientId); } /** @@ -670,7 +681,7 @@ public class SaOAuth2Template { } /** - * 回收 ClientToken + * 回收一个 ClientToken * * @param clientToken / */ @@ -682,10 +693,11 @@ public class SaOAuth2Template { // 删 ct、删索引 SaOAuth2Dao dao = SaOAuth2Manager.getDao(); dao.deleteClientToken(clientToken); - dao.deleteClientTokenIndex(ct.clientId); + dao.deleteClientTokenIndexBySingleData(ct.clientId, clientToken); } /** + * 回收全部 Client-Token:指定应用下的全部 Client-Token * 回收 ClientToken,根据索引: clientId * * @param clientId / @@ -693,29 +705,17 @@ public class SaOAuth2Template { public void revokeClientTokenByIndex(String clientId) { SaOAuth2Dao dao = SaOAuth2Manager.getDao(); - // 删 clientToken - String clientToken = getClientTokenValue(clientId); - if(clientToken != null) { - dao.deleteClientToken(clientToken); + List clientTokenList = getClientTokenValueList(clientId); + if( ! clientTokenList.isEmpty()) { + // 删 AT + for (String clientToken : clientTokenList) { + dao.deleteClientToken(clientToken); + } + // 删索引 dao.deleteClientTokenIndex(clientId); } } - /** - * 回收 Lower-Client-Token,根据索引: clientId - * - * @param clientId / - */ - public void revokeLowerClientTokenByIndex(String clientId) { - SaOAuth2Dao dao = SaOAuth2Manager.getDao(); - // 删 Lower-Client-Token - String lowerClientToken = dao.getLowerClientTokenValue(clientId); - if(lowerClientToken != null) { - dao.deleteLowerClientToken(lowerClientToken); - dao.deleteLowerClientTokenIndex(clientId); - } - } - // ----------------- 包装其它 bean 的方法 ----------------- 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 f0616f33..ffcbf239 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 @@ -181,13 +181,13 @@ public class SaOAuth2Util { } /** - * 获取 Access-Token,根据索引: clientId、loginId + * 获取 Access-Token 列表:此应用下 对 某个用户 签发的所有 Access-token * @param clientId / * @param loginId / * @return / */ - public static String getAccessTokenValue(String clientId, Object loginId) { - return SaOAuth2Manager.getTemplate().getAccessTokenValue(clientId, loginId); + public static List getAccessTokenValueList(String clientId, Object loginId) { + return SaOAuth2Manager.getTemplate().getAccessTokenValueList(clientId, loginId); } /** @@ -227,7 +227,7 @@ public class SaOAuth2Util { } /** - * 回收 Access-Token + * 回收一个 Access-Token * @param accessToken Access-Token值 */ public static void revokeAccessToken(String accessToken) { @@ -235,7 +235,7 @@ public class SaOAuth2Util { } /** - * 回收 Access-Token,根据索引: clientId、loginId + * 回收全部 Access-Token:指定应用下 指定用户 的全部 Access-Token * @param clientId / * @param loginId / */ @@ -265,17 +265,19 @@ public class SaOAuth2Util { } /** - * 获取 Refresh-Token,根据索引: clientId、loginId + * 获取 Refresh-Token 列表:此应用下 对 某个用户 签发的所有 Refresh-Token + * * @param clientId / * @param loginId / * @return / */ - public static String getRefreshTokenValue(String clientId, Object loginId) { - return SaOAuth2Manager.getTemplate().getRefreshTokenValue(clientId, loginId); + public static List getRefreshTokenValueList(String clientId, Object loginId) { + return SaOAuth2Manager.getTemplate().getRefreshTokenValueList(clientId, loginId); } /** - * 回收 Refresh-Token + * 回收一个 Refresh-Token + * * @param refreshToken Refresh-Token 值 */ public static void revokeRefreshToken(String refreshToken) { @@ -283,7 +285,7 @@ public class SaOAuth2Util { } /** - * 回收 Refresh-Token,根据索引: clientId、loginId + * 回收全部 Refresh-Token:指定应用下 指定用户 的全部 Refresh-Token * @param clientId / * @param loginId / */ @@ -322,12 +324,13 @@ public class SaOAuth2Util { } /** - * 获取 ClientToken,根据索引: clientId + * 获取 Client-Token 列表:此应用下 对 某个用户 签发的所有 Client-token + * * @param clientId / * @return / */ - public static String getClientTokenValue(String clientId) { - return SaOAuth2Manager.getTemplate().getClientTokenValue(clientId); + public static List getClientTokenValueList(String clientId) { + return SaOAuth2Manager.getTemplate().getClientTokenValueList(clientId); } /** @@ -349,7 +352,7 @@ public class SaOAuth2Util { } /** - * 回收 ClientToken + * 回收一个 ClientToken * * @param clientToken / */ @@ -358,7 +361,7 @@ public class SaOAuth2Util { } /** - * 回收 ClientToken,根据索引: clientId + * 回收全部 Client-Token:指定应用下的全部 Client-Token * * @param clientId / */ @@ -366,13 +369,4 @@ public class SaOAuth2Util { SaOAuth2Manager.getTemplate().revokeClientTokenByIndex(clientId); } - /** - * 回收 Lower-Client-Token,根据索引: clientId - * - * @param clientId / - */ - public static void revokeLowerClientTokenByIndex(String clientId) { - SaOAuth2Manager.getTemplate().revokeLowerClientTokenByIndex(clientId); - } - }