feat: 重构 temp token 模块,新增 value 反查 token 机制

This commit is contained in:
click33 2025-04-09 08:15:46 +08:00
parent 14e645a8ac
commit 9ecaf72e9f
31 changed files with 1042 additions and 459 deletions

View File

@ -41,8 +41,7 @@ import cn.dev33.satoken.stp.StpInterfaceDefaultImpl;
import cn.dev33.satoken.stp.StpLogic; import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.strategy.SaStrategy; import cn.dev33.satoken.strategy.SaStrategy;
import cn.dev33.satoken.temp.SaTempDefaultImpl; import cn.dev33.satoken.temp.SaTempTemplate;
import cn.dev33.satoken.temp.SaTempInterface;
import cn.dev33.satoken.util.SaFoxUtil; import cn.dev33.satoken.util.SaFoxUtil;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@ -167,20 +166,20 @@ public class SaManager {
/** /**
* 临时 token 认证模块 * 临时 token 认证模块
*/ */
private volatile static SaTempInterface saTemp; private volatile static SaTempTemplate saTempTemplate;
public static void setSaTemp(SaTempInterface saTemp) { public static void setSaTempTemplate(SaTempTemplate saTempTemplate) {
SaManager.saTemp = saTemp; SaManager.saTempTemplate = saTempTemplate;
SaTokenEventCenter.doRegisterComponent("SaTempInterface", saTemp); SaTokenEventCenter.doRegisterComponent("SaTempInterface", saTempTemplate);
} }
public static SaTempInterface getSaTemp() { public static SaTempTemplate getSaTempTemplate() {
if (saTemp == null) { if (saTempTemplate == null) {
synchronized (SaManager.class) { synchronized (SaManager.class) {
if (saTemp == null) { if (saTempTemplate == null) {
SaManager.saTemp = new SaTempDefaultImpl(); SaManager.saTempTemplate = new SaTempTemplate();
} }
} }
} }
return saTemp; return saTempTemplate;
} }
/** /**

View File

@ -26,7 +26,7 @@ import cn.dev33.satoken.exception.ApiKeyException;
import cn.dev33.satoken.exception.ApiKeyScopeException; import cn.dev33.satoken.exception.ApiKeyScopeException;
import cn.dev33.satoken.httpauth.basic.SaHttpBasicUtil; import cn.dev33.satoken.httpauth.basic.SaHttpBasicUtil;
import cn.dev33.satoken.session.SaSession; import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.session.SaSessionRawUtil; import cn.dev33.satoken.session.raw.SaRawSessionDelegator;
import cn.dev33.satoken.strategy.SaStrategy; import cn.dev33.satoken.strategy.SaStrategy;
import cn.dev33.satoken.util.SaFoxUtil; import cn.dev33.satoken.util.SaFoxUtil;
@ -42,9 +42,9 @@ import java.util.List;
public class SaApiKeyTemplate { public class SaApiKeyTemplate {
/** /**
* ApiKey raw-session 类型 * Raw Session 读写委托
*/ */
public static final String SESSION_TYPE = "apikey"; public SaRawSessionDelegator rawSessionDelegator = new SaRawSessionDelegator("apikey");
/** /**
* raw-session 中的保存索引列表使用的 key * raw-session 中的保存索引列表使用的 key
@ -132,10 +132,10 @@ public class SaApiKeyTemplate {
getSaTokenDao().setObject(saveKey, ak, ak.expiresIn()); getSaTokenDao().setObject(saveKey, ak, ak.expiresIn());
} }
// 调整索引 // 记录索引
if (SaManager.getSaApiKeyDataLoader().getIsRecordIndex()) { if (SaManager.getSaApiKeyDataLoader().getIsRecordIndex()) {
// 记录索引 // 添加索引
SaSession session = SaSessionRawUtil.getSessionById(SESSION_TYPE, ak.getLoginId()); SaSession session = rawSessionDelegator.getSessionById(ak.getLoginId());
ArrayList<String> apiKeyList = session.get(API_KEY_LIST, ArrayList::new); ArrayList<String> apiKeyList = session.get(API_KEY_LIST, ArrayList::new);
if(! apiKeyList.contains(ak.getApiKey())) { if(! apiKeyList.contains(ak.getApiKey())) {
apiKeyList.add(ak.getApiKey()); apiKeyList.add(ak.getApiKey());
@ -172,7 +172,7 @@ public class SaApiKeyTemplate {
// 删索引 // 删索引
if(SaManager.getSaApiKeyDataLoader().getIsRecordIndex()) { if(SaManager.getSaApiKeyDataLoader().getIsRecordIndex()) {
// RawSession 中不存在提前退出 // RawSession 中不存在提前退出
SaSession session = SaSessionRawUtil.getSessionById(SESSION_TYPE, ak.getLoginId(), false); SaSession session = rawSessionDelegator.getSessionById(ak.getLoginId(), false);
if(session == null) { if(session == null) {
return; return;
} }
@ -184,7 +184,7 @@ public class SaApiKeyTemplate {
// 如果只有一个 ApiKey则整个 RawSession 删掉 // 如果只有一个 ApiKey则整个 RawSession 删掉
if (apiKeyList.size() == 1) { if (apiKeyList.size() == 1) {
SaSessionRawUtil.deleteSessionById(SESSION_TYPE, ak.getLoginId()); rawSessionDelegator.deleteSessionById(ak.getLoginId());
} else { } else {
// 否则移除此 ApiKey 并保存 // 否则移除此 ApiKey 并保存
apiKeyList.remove(apiKey); apiKeyList.remove(apiKey);
@ -205,7 +205,7 @@ public class SaApiKeyTemplate {
} }
// RawSession 中不存在提前退出 // RawSession 中不存在提前退出
SaSession session = SaSessionRawUtil.getSessionById(SESSION_TYPE, loginId, false); SaSession session = rawSessionDelegator.getSessionById(loginId, false);
if(session == null) { if(session == null) {
return; return;
} }
@ -217,7 +217,7 @@ public class SaApiKeyTemplate {
} }
// 再删索引 // 再删索引
SaSessionRawUtil.deleteSessionById(SESSION_TYPE, loginId); rawSessionDelegator.deleteSessionById(loginId);
} }
// ------- 创建 // ------- 创建
@ -382,7 +382,7 @@ public class SaApiKeyTemplate {
// 未提供则现场查询 // 未提供则现场查询
if(session == null) { if(session == null) {
session = SaSessionRawUtil.getSessionById(SESSION_TYPE, loginId, false); session = rawSessionDelegator.getSessionById(loginId, false);
if(session == null) { if(session == null) {
return; return;
} }
@ -400,6 +400,11 @@ public class SaApiKeyTemplate {
apiKeyNewList.add(apikey); apiKeyNewList.add(apikey);
apiKeyModelList.add(ak); apiKeyModelList.add(ak);
} }
// 如果队列里已无有效值则删除该 session
if(apiKeyNewList.isEmpty()) {
rawSessionDelegator.deleteSessionById(loginId);
return;
}
session.set(API_KEY_LIST, apiKeyNewList); session.set(API_KEY_LIST, apiKeyNewList);
// 调整 SaSession TTL // 调整 SaSession TTL
@ -433,7 +438,7 @@ public class SaApiKeyTemplate {
// 先查 RawSession // 先查 RawSession
List<ApiKeyModel> apiKeyModelList = new ArrayList<>(); List<ApiKeyModel> apiKeyModelList = new ArrayList<>();
SaSession session = SaSessionRawUtil.getSessionById(SESSION_TYPE, loginId, false); SaSession session = rawSessionDelegator.getSessionById(loginId, false);
if(session == null) { if(session == null) {
return apiKeyModelList; return apiKeyModelList;
} }

View File

@ -16,6 +16,7 @@
package cn.dev33.satoken.application; package cn.dev33.satoken.application;
import cn.dev33.satoken.fun.SaRetFunction; import cn.dev33.satoken.fun.SaRetFunction;
import cn.dev33.satoken.fun.SaRetGenericFunction;
/** /**
* 对写值的一组方法封装 * 对写值的一组方法封装
@ -55,7 +56,7 @@ public interface SaSetValueInterface extends SaGetValueInterface {
* @return * @return
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
default <T> T get(String key, SaRetFunction fun) { default <T> T get(String key, SaRetGenericFunction<T> fun) {
Object value = get(key); Object value = get(key);
if(value == null) { if(value == null) {
value = fun.run(); value = fun.run();

View File

@ -0,0 +1,77 @@
/*
* 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.session.raw;
import cn.dev33.satoken.session.SaSession;
/**
* SaSession 读写工具类 委托
*
* @author click33
* @since 1.42.0
*/
public class SaRawSessionDelegator {
/**
* raw session 类型
*/
public String type;
public SaRawSessionDelegator(String type) {
this.type = type;
}
/**
* 判断指定 SaSession 是否存在
*
* @param valueId /
* @return 是否存在
*/
public boolean isExists(Object valueId) {
return SaRawSessionUtil.isExists(type, valueId);
}
/**
* 获取指定 SaSession 对象, 如果此 SaSession 尚未在 Cache 创建isCreate 参数代表是否则新建并返回
*
* @param valueId /
* @param isCreate 如果此 SaSession 尚未在 DB 创建是否新建并返回
* @return SaSession 对象
*/
public SaSession getSessionById(Object valueId, boolean isCreate) {
return SaRawSessionUtil.getSessionById(type, valueId, isCreate);
}
/**
* 获取指定 SaSession, 如果此 SaSession 尚未在 DB 创建则新建并返回
*
* @param valueId /
* @return SaSession 对象
*/
public SaSession getSessionById(Object valueId) {
return SaRawSessionUtil.getSessionById(type, valueId);
}
/**
* 删除指定 SaSession
*
* @param valueId /
*/
public void deleteSessionById(Object valueId) {
SaRawSessionUtil.deleteSessionById(type, valueId);
}
}

View File

@ -13,9 +13,10 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package cn.dev33.satoken.session; package cn.dev33.satoken.session.raw;
import cn.dev33.satoken.SaManager; import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.strategy.SaStrategy; import cn.dev33.satoken.strategy.SaStrategy;
/** /**
@ -24,9 +25,9 @@ import cn.dev33.satoken.strategy.SaStrategy;
* @author click33 * @author click33
* @since 1.42.0 * @since 1.42.0
*/ */
public class SaSessionRawUtil { public class SaRawSessionUtil {
private SaSessionRawUtil() { private SaRawSessionUtil() {
} }
/** /**

View File

@ -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.temp;
/**
* Sa-Token 临时令牌验证模块 默认实现类
*
* @author click33
* @since 1.20.0
*/
public class SaTempDefaultImpl implements SaTempInterface {
}

View File

@ -1,173 +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.temp;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.strategy.SaStrategy;
import cn.dev33.satoken.util.SaFoxUtil;
import cn.dev33.satoken.util.SaTokenConsts;
/**
* Sa-Token 临时 token 验证模块 - 接口
*
* <p>
* 有效期很短的一种token一般用于一次性接口防盗用短时间资源访问等业务场景
* </p>
*
* @author click33
* @since 1.20.0
*/
public interface SaTempInterface {
// -------- 创建
/**
* 为指定 value 创建一个临时 Token
* @param value 指定值
* @param timeout 有效时间单位-1 代表永久有效
* @return 生成的 token
*/
default String createToken(Object value, long timeout) {
return createToken(SaTokenConsts.DEFAULT_TEMP_TOKEN_SERVICE, value, timeout);
}
/**
* 为指定 业务标识指定 value 创建一个 Token
* @param service 业务标识
* @param value 指定值
* @param timeout 有效期单位-1 代表永久有效
* @return 生成的token
*/
default String createToken(String service, Object value, long timeout) {
// 生成 token
String token = SaStrategy.instance.createToken.apply(null, null);
// 持久化映射关系
String key = splicingKeyTempToken(service, token);
SaManager.getSaTokenDao().setObject(key, value, timeout);
// 返回
return token;
}
// -------- 解析
/**
* 解析 Token 获取 value
* @param token 指定 Token
* @return /
*/
default Object parseToken(String token) {
return parseToken(SaTokenConsts.DEFAULT_TEMP_TOKEN_SERVICE, token);
}
/**
* 解析 Token 获取 value
* @param service 业务标识
* @param token 指定 Token
* @return /
*/
default Object parseToken(String service, String token) {
String key = splicingKeyTempToken(service, token);
return SaManager.getSaTokenDao().getObject(key);
}
/**
* 解析 Token 获取 value并转换为指定类型
* @param token 指定 Token
* @param cs 指定类型
* @param <T> 默认值的类型
* @return /
*/
default<T> T parseToken(String token, Class<T> cs) {
return parseToken(SaTokenConsts.DEFAULT_TEMP_TOKEN_SERVICE, token, cs);
}
/**
* 解析 Token 获取 value并转换为指定类型
* @param service 业务标识
* @param token 指定 Token
* @param cs 指定类型
* @param <T> 默认值的类型
* @return /
*/
default<T> T parseToken(String service, String token, Class<T> cs) {
return SaFoxUtil.getValueByType(parseToken(service, token), cs);
}
/**
* 获取指定 Token 的剩余有效期单位
* <p> 返回值 -1 代表永久-2 代表token无效
* @param token 指定 Token
* @return /
*/
default long getTimeout(String token) {
return getTimeout(SaTokenConsts.DEFAULT_TEMP_TOKEN_SERVICE, token);
}
/**
* 获取指定 业务标识指定 Token 的剩余有效期单位
* <p> 返回值 -1 代表永久-2 代表token无效
* @param service 业务标识
* @param token 指定 Token
* @return /
*/
default long getTimeout(String service, String token) {
String key = splicingKeyTempToken(service, token);
return SaManager.getSaTokenDao().getObjectTimeout(key);
}
// -------- 删除
/**
* 删除一个 Token
* @param token 指定 Token
*/
default void deleteToken(String token) {
deleteToken(SaTokenConsts.DEFAULT_TEMP_TOKEN_SERVICE, token);
}
/**
* 删除一个 Token
* @param service 业务标识
* @param token 指定 Token
*/
default void deleteToken(String service, String token) {
String key = splicingKeyTempToken(service, token);
SaManager.getSaTokenDao().deleteObject(key);
}
// -------- 其它
/**
* 获取在存储临时 token 数据时应该使用的 key
* @param service 业务标识
* @param token token值
* @return key
*/
default String splicingKeyTempToken(String service, String token) {
return SaManager.getConfig().getTokenName() + ":temp-token:" + service + ":" + token;
}
/**
* @return jwt秘钥 (只有集成 sa-token-temp-jwt 模块时此参数才会生效)
*/
default String getJwtSecretKey() {
return null;
}
}

View File

@ -0,0 +1,382 @@
/*
* 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.temp;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.session.raw.SaRawSessionDelegator;
import cn.dev33.satoken.strategy.SaStrategy;
import cn.dev33.satoken.util.SaFoxUtil;
import java.util.*;
/**
* Sa-Token 临时 token 验证模块
*
* <p>
* 有效期很短的一种token一般用于一次性接口防盗用短时间资源访问等业务场景
* </p>
*
* @author click33
* @since 1.42.0
*/
public class SaTempTemplate {
/**
* Raw Session 读写委托
*/
public SaRawSessionDelegator rawSessionDelegator = new SaRawSessionDelegator("temp-token");
/**
* raw-session 中的保存索引列表使用的 key
*/
public static final String TEMP_TOKEN_MAP = "__HD_TEMP_TOKEN_MAP";
// -------- 创建
/**
* 为指定 value 创建一个临时 token (如果多条业务线均需要创建临时 token请自行在 value 拼接不同前缀)
*
* @param value 指定值
* @param timeout 有效时间单位-1 代表永久有效
* @return 生成的 token
*/
public String createToken(Object value, long timeout) {
return createToken(value, timeout, false);
}
/**
* 为指定 业务标识指定 value 创建一个 Token
* @param value 指定值
* @param timeout 有效期单位-1 代表永久有效
* @param isRecordIndex 是否记录索引以便后续使用 value 反查 token
* @return 生成的token
*/
public String createToken(Object value, long timeout, boolean isRecordIndex) {
// 生成 temp-token
String tempToken = createTempTokenValue(value);
// 持久化映射关系
_saveToken(tempToken, value, timeout);
// 记录索引
if(isRecordIndex) {
SaSession session = rawSessionDelegator.getSessionById(value);
addTempTokenIndex(session, tempToken, timeout);
adjustIndex(value, session);
}
// 返回
return tempToken;
}
/**
* 创建一个 temp-token
*
* @return /
*/
public String createTempTokenValue(Object value) {
return SaStrategy.instance.generateUniqueToken.execute(
"Temp Token",
SaManager.getConfig().getMaxTryTimes(),
() -> randomTempToken(value),
_apiKey -> parseToken(_apiKey) == null
);
}
/**
* 随机一个 temp-token
*
* @return /
*/
public String randomTempToken(Object value) {
return UUID.randomUUID().toString().replace("-", "");
}
// -------- 解析
/**
* 解析 Token 获取 value
* @param token 指定 Token
* @return /
*/
public Object parseToken(String token) {
return _getValue(token);
}
/**
* 解析 Token 获取 value并转换为指定类型
*
* @param token 指定 Token
* @param cs 指定类型
* @param <T> 默认值的类型
* @return /
*/
public<T> T parseToken(String token, Class<T> cs) {
return parseToken(token, null, cs);
}
/**
* 解析 token 获取 value并裁剪指定前缀然后转换为指定类型
*
* @param token 指定 Token
* @param cs 指定类型
* @param <T> 默认值的类型
* @return /
*/
public<T> T parseToken(String token, String cutPrefix, Class<T> cs) {
// 解析值
Object value = parseToken(token);
// 如果未指定裁剪前缀则直接返回
if(SaFoxUtil.isEmpty(cutPrefix)) {
return SaFoxUtil.getValueByType(value, cs);
}
// 如果符合前缀则裁剪并返回如果不符合前缀则返回 null
String str = SaFoxUtil.valueToString(value);
if(str.startsWith(cutPrefix)) {
return SaFoxUtil.getValueByType(str.substring(cutPrefix.length()), cs);
} else {
return null;
}
}
/**
* 获取指定指定 Token 的剩余有效期单位
* <p> 返回值 -1 代表永久-2 代表 token 无效
*
* @param token /
* @return /
*/
public long getTimeout(String token) {
return _getTimeout(token);
}
// -------- 删除
/**
* 删除一个 token
* @param token 指定 Token
*/
public void deleteToken(String token) {
// 如果无此数据则直接返回
Object value = parseToken(token);
if(SaFoxUtil.isEmpty(value)) {
return;
}
// 删除 token 本身
_deleteToken(token);
// 调整索引
SaSession session = rawSessionDelegator.getSessionById(value, false);
if(session != null) {
deleteTempTokenIndex(session, token);
adjustIndex(value, null);
}
}
// ------------------- 索引操作
/**
* 调整索引
*
* @param value
* @param session 可填写 null代表使用 value 现场查询
* @return 调整后的索引列表
*/
public Map<String, Long> adjustIndex(Object value, SaSession session) {
// 未提供则现场查询
if(session == null) {
session = rawSessionDelegator.getSessionById(value, false);
if(session == null) {
return newTempTokenMap();
}
}
// 重新整理索引列表
Map<String, Long> tempTokenNewList = newTempTokenMap();
ArrayList<Long> tempTokenTtlList = new ArrayList<>();
Map<String, Long> tempTokenMap = session.get(TEMP_TOKEN_MAP, this::newTempTokenMap);
for (Map.Entry<String, Long> entry : tempTokenMap.entrySet()) {
long ttl = expireTimeToTtl(entry.getValue());
if(ttl != SaTokenDao.NOT_VALUE_EXPIRE) {
tempTokenNewList.put(entry.getKey(), entry.getValue());
tempTokenTtlList.add(ttl);
}
}
// 有则保存无则删除
if( ! tempTokenNewList.isEmpty()) {
session.set(TEMP_TOKEN_MAP, tempTokenNewList);
} else {
rawSessionDelegator.deleteSessionById(value);
return tempTokenNewList;
}
// 调整 SaSession TTL
long maxTtl = getMaxTtl(tempTokenTtlList);
if(maxTtl != 0) {
session.updateTimeout(maxTtl);
}
return tempTokenNewList;
}
/**
* 获取指定 value temp-token 列表记录
* @param value /
* @return /
*/
public List<String> getTempTokenList(Object value) {
// 先调增索引再获取否则有可能获取到的不是最新有效数据
Map<String, Long> tempTokenMap = adjustIndex(value, null);
return new ArrayList<>(tempTokenMap.keySet());
}
/**
* SaSession 上添加临时 temp-token 索引
* @param session /
* @param token /
* @param timeout /
*/
protected void addTempTokenIndex(SaSession session, String token, long timeout) {
Map<String, Long> tempTokenMap = session.get(TEMP_TOKEN_MAP, this::newTempTokenMap);
if(! tempTokenMap.containsKey(token)) {
tempTokenMap.put(token, ttlToExpireTime(timeout));
session.set(TEMP_TOKEN_MAP, tempTokenMap);
}
}
/**
* SaSession 上删除临时 temp-token 索引
* @param session /
* @param token /
*/
protected void deleteTempTokenIndex(SaSession session, String token) {
Map<String, Long> tempTokenMap = session.get(TEMP_TOKEN_MAP, this::newTempTokenMap);
if(tempTokenMap.containsKey(token)) {
tempTokenMap.remove(token);
session.set(TEMP_TOKEN_MAP, tempTokenMap);
}
}
/**
* 获取一个新的 TempTokenMap 集合
* @return /
*/
protected Map<String, Long> newTempTokenMap() {
return new LinkedHashMap<>();
}
// -------- 元操作
protected Object _getValue(String token) {
String key = splicingTempTokenSaveKey(token);
return SaManager.getSaTokenDao().getObject(key);
}
protected void _saveToken(String token, Object value, long timeout) {
String key = splicingTempTokenSaveKey(token);
SaManager.getSaTokenDao().setObject(key, value, timeout);
}
protected void _deleteToken(String token) {
String key = splicingTempTokenSaveKey(token);
SaManager.getSaTokenDao().deleteObject(key);
}
protected long _getTimeout(String token) {
String key = splicingTempTokenSaveKey(token);
return SaManager.getSaTokenDao().getObjectTimeout(key);
}
// -------- 其它
/**
* 过期时间转 ttl () 获取最大 ttl
* @param tempTokenTtlList /
* @return /
*/
protected long getMaxTtl(ArrayList<Long> tempTokenTtlList) {
long maxTtl = 0;
for (long ttl : tempTokenTtlList) {
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;
}
return (expireTime - System.currentTimeMillis()) / 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();
}
/**
* 获取在存储临时 token 数据时应该使用的 key
* @param token token值
* @return key
*/
public String splicingTempTokenSaveKey(String token) {
return SaManager.getConfig().getTokenName() + ":temp-token:" + token;
}
/**
* @return jwt秘钥 (只有集成 sa-token-temp-jwt 模块时此参数才会生效)
*/
public String getJwtSecretKey() {
return null;
}
}

View File

@ -17,6 +17,8 @@ package cn.dev33.satoken.temp;
import cn.dev33.satoken.SaManager; import cn.dev33.satoken.SaManager;
import java.util.List;
/** /**
* Sa-Token 临时 token 验证模块 - 工具类 * Sa-Token 临时 token 验证模块 - 工具类
* *
@ -35,108 +37,94 @@ public class SaTempUtil {
// -------- 创建 // -------- 创建
/** /**
* 为指定 value 创建一个临时 Token * 为指定 value 创建一个临时 token (如果多条业务线均需要创建临时 token请自行在 value 拼接不同前缀)
*
* @param value 指定值 * @param value 指定值
* @param timeout 有效单位-1 代表永久有效 * @param timeout 有效时间单位-1 代表永久有效
* @return 生成的token * @return 生成的 token
*/ */
public static String createToken(Object value, long timeout) { public static String createToken(Object value, long timeout) {
return SaManager.getSaTemp().createToken(value, timeout); return SaManager.getSaTempTemplate().createToken(value, timeout);
} }
/** /**
* 为指定 业务标识指定 value 创建一个 Token * 为指定 业务标识指定 value 创建一个 Token
* @param service 业务标识
* @param value 指定值 * @param value 指定值
* @param timeout 有效期单位-1 代表永久有效 * @param timeout 有效期单位-1 代表永久有效
* @param isRecordIndex 是否记录索引以便后续使用 value 反查 token
* @return 生成的token * @return 生成的token
*/ */
public static String createToken(String service, Object value, long timeout) { public static String createToken(Object value, long timeout, boolean isRecordIndex) {
return SaManager.getSaTemp().createToken(service, value, timeout); return SaManager.getSaTempTemplate().createToken(value, timeout, isRecordIndex);
} }
// -------- 解析 // -------- 解析
/** /**
* 解析 Token 获取 value * 解析 Token 获取 value
* @param token 指定 Token * @param token 指定 Token
* @return / * @return /
*/ */
public static Object parseToken(String token) { public static Object parseToken(String token) {
return SaManager.getSaTemp().parseToken(token); return SaManager.getSaTempTemplate().parseToken(token);
}
/**
* 解析 Token 获取 value
* @param service 业务标识
* @param token 指定 Token
* @return /
*/
public static Object parseToken(String service, String token) {
return SaManager.getSaTemp().parseToken(service, token);
}
/**
* 解析 Token 获取 value并转换为指定类型
* @param token 指定 Token
* @param cs 指定类型
* @param <T> 默认值的类型
* @return /
*/
public static<T> T parseToken(String token, Class<T> cs) {
return SaManager.getSaTemp().parseToken(token, cs);
} }
/** /**
* 解析 Token 获取 value并转换为指定类型 * 解析 Token 获取 value并转换为指定类型
* @param service 业务标识 *
* @param token 指定 Token
* @param cs 指定类型
* @param <T> 默认值的类型
* @return /
*/
public static<T> T parseToken(String service, String token, Class<T> cs) {
return SaManager.getSaTemp().parseToken(service, token, cs);
}
/**
* 获取指定 Token 的剩余有效期单位
* <p> 返回值 -1 代表永久-2 代表token无效
* @param token 指定 Token * @param token 指定 Token
* @param cs 指定类型
* @param <T> 默认值的类型
* @return / * @return /
*/ */
public static long getTimeout(String token) { public static<T> T parseToken(String token, Class<T> cs) {
return SaManager.getSaTemp().getTimeout(token); return SaManager.getSaTempTemplate().parseToken(token, cs);
} }
/** /**
* 获取指定 业务标识指定 Token 的剩余有效期单位 * 解析 token 获取 value并裁剪指定前缀然后转换为指定类型
* <p> 返回值 -1 代表永久-2 代表token无效 *
* @param service 业务标识
* @param token 指定 Token * @param token 指定 Token
* @return / * @param cs 指定类型
* @param <T> 默认值的类型
* @return /
*/ */
public static long getTimeout(String service, String token) { public static<T> T parseToken(String token, String cutPrefix, Class<T> cs) {
return SaManager.getSaTemp().getTimeout(service, token); return SaManager.getSaTempTemplate().parseToken(token, cutPrefix, cs);
} }
/**
* 获取指定指定 Token 的剩余有效期单位
* <p> 返回值 -1 代表永久-2 代表 token 无效
*
* @param token /
* @return /
*/
public static long getTimeout(String token) {
return SaManager.getSaTempTemplate().getTimeout(token);
}
// -------- 删除 // -------- 删除
/** /**
* 删除一个 Token * 删除一个 token
* @param token 指定 Token * @param token 指定 Token
*/ */
public static void deleteToken(String token) { public static void deleteToken(String token) {
SaManager.getSaTemp().deleteToken(token); SaManager.getSaTempTemplate().deleteToken(token);
} }
// ------------------- 索引操作
/** /**
* 删除一个 Token * 获取指定 value temp-token 列表记录
* @param service 业务标识 * @param value /
* @param token 指定 Token * @return /
*/ */
public static void deleteToken(String service, String token) { public static List<String> getTempTokenList(Object value) {
SaManager.getSaTemp().deleteToken(service, token); return SaManager.getSaTempTemplate().getTempTokenList(value);
} }
} }

View File

@ -828,5 +828,17 @@ public class SaFoxUtil {
return false; return false;
} }
/**
* value 转化为 String如果 value null则返回空字符串
* @param value /
* @return /
*/
public static String valueToString(Object value) {
if (value == null) {
return "";
}
return value.toString();
}
} }

View File

@ -120,6 +120,7 @@ public class SaTokenConsts {
/** /**
* 常量 key 标记: 临时 Token 认证模块默认的业务类型 * 常量 key 标记: 临时 Token 认证模块默认的业务类型
*/ */
@Deprecated
public static final String DEFAULT_TEMP_TOKEN_SERVICE = "record"; public static final String DEFAULT_TEMP_TOKEN_SERVICE = "record";

View File

@ -59,6 +59,13 @@
<artifactId>commons-pool2</artifactId> <artifactId>commons-pool2</artifactId>
</dependency> </dependency>
<!-- Sa-Token 临时 token 模块整合 jwt -->
<!--<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-temp-jwt</artifactId>
<version>${sa-token.version}</version>
</dependency>-->
<!-- @ConfigurationProperties --> <!-- @ConfigurationProperties -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>

View File

@ -0,0 +1,69 @@
package com.pj.cases.plugin;
import cn.dev33.satoken.temp.SaTempUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 测试专用 Controller
* @author click33
*
*/
@RestController
@RequestMapping("/temp-token/")
public class TempTokenController {
// 创建 浏览器访问 http://localhost:8081/temp-token/create
@RequestMapping("create")
public SaResult create() {
String token = SaTempUtil.createToken(10001, 1200);
System.out.println("创建成功:" + token);
return SaResult.data(token);
}
// 创建获取时裁剪前缀 浏览器访问 http://localhost:8081/temp-token/create2
@RequestMapping("create2")
public SaResult create2() {
String token = SaTempUtil.createToken("shop_" + 1001, 1200);
System.out.println("创建成功:" + token);
System.out.println("获取对应值:" + SaTempUtil.parseToken(token));
System.out.println("获取对应值,并裁剪前缀:" + SaTempUtil.parseToken(token, "shop_", Long.class));
System.out.println("指定错误前缀来获取:" + SaTempUtil.parseToken(token, "art_", Long.class));
return SaResult.data(token);
}
// 创建回收 浏览器访问 http://localhost:8081/temp-token/create3
@RequestMapping("create3")
public SaResult create3() {
String token = SaTempUtil.createToken(10003, 1200);
System.out.println("创建成功:" + token);
System.out.println("获取对应值:" + SaTempUtil.parseToken(token));
SaTempUtil.deleteToken(token);
System.out.println("回收后再获取:" + SaTempUtil.parseToken(token));
return SaResult.data(token);
}
// 创建记录索引 浏览器访问 http://localhost:8081/temp-token/create4
@RequestMapping("create4")
public SaResult create4() {
String token1 = SaTempUtil.createToken(10004, 1200, true);
String token2 = SaTempUtil.createToken(10004, 1300, true);
String token3 = SaTempUtil.createToken(10004, -1, true);
System.out.println("token1 剩余有效期:" + SaTempUtil.getTimeout(token1));
System.out.println("token2 剩余有效期:" + SaTempUtil.getTimeout(token2));
System.out.println("token3 剩余有效期:" + SaTempUtil.getTimeout(token3));
SaTempUtil.deleteToken(token3);
// 获取已创建的 token 列表
System.out.println("获取已创建的 token 列表 ");
List<String> tempTokenList = SaTempUtil.getTempTokenList(10004);
System.out.println(tempTokenList);
return SaResult.data(token1);
}
}

View File

@ -1,10 +1,9 @@
package com.pj.cases.up; package com.pj.cases.up;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult; import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/** /**
* Sa-Token 同端互斥登录示例 * Sa-Token 同端互斥登录示例
@ -62,7 +61,7 @@ public class MutexLoginController {
public SaResult isLogin() { public SaResult isLogin() {
// StpUtil.isLogin() 查询当前客户端是否登录返回 true false // StpUtil.isLogin() 查询当前客户端是否登录返回 true false
boolean isLogin = StpUtil.isLogin(); boolean isLogin = StpUtil.isLogin();
return SaResult.ok("当前客户端是否登录:" + isLogin + ",登录的设备是:" + StpUtil.getLoginDevice()); return SaResult.ok("当前客户端是否登录:" + isLogin + ",登录的设备是:" + StpUtil.getLoginDeviceType());
} }
} }

View File

@ -18,6 +18,8 @@ sa-token:
token-style: uuid token-style: uuid
# 是否输出操作日志 # 是否输出操作日志
is-log: true is-log: true
# jwt 秘钥
jwt-secret-key: JfdDSgfCmPsDfmsAaQwnXk
spring: spring:
# redis配置 # redis配置

View File

@ -34,7 +34,7 @@
<dubbo.version>2.7.21</dubbo.version> <dubbo.version>2.7.21</dubbo.version>
<grpc-spring-boot-starter.version>2.10.1.RELEASE</grpc-spring-boot-starter.version> <grpc-spring-boot-starter.version>2.10.1.RELEASE</grpc-spring-boot-starter.version>
<hutool-jwt.version>5.8.36</hutool-jwt.version> <hutool-jwt.version>5.8.36</hutool-jwt.version>
<jjwt.version>0.9.1</jjwt.version> <jjwt.version>0.12.6</jjwt.version>
<fastjson.version>1.2.83</fastjson.version> <fastjson.version>1.2.83</fastjson.version>
<fastjson2.version>2.0.15</fastjson2.version> <fastjson2.version>2.0.15</fastjson2.version>
<redisson.version>3.45.0</redisson.version> <redisson.version>3.45.0</redisson.version>
@ -253,12 +253,12 @@
<artifactId>grpc-spring-boot-starter</artifactId> <artifactId>grpc-spring-boot-starter</artifactId>
<version>${grpc-spring-boot-starter.version}</version> <version>${grpc-spring-boot-starter.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.jsonwebtoken</groupId> <groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId> <artifactId>jjwt</artifactId>
<version>${jjwt.version}</version> <version>${jjwt.version}</version>
</dependency> </dependency>
<!-- Hutool Cache --> <!-- Hutool Cache -->
<dependency> <dependency>

View File

@ -22,10 +22,10 @@
<groupId>cn.dev33</groupId> <groupId>cn.dev33</groupId>
<artifactId>sa-token-core</artifactId> <artifactId>sa-token-core</artifactId>
</dependency> </dependency>
<!-- jwt --> <!-- jwt -->
<dependency> <dependency>
<groupId>io.jsonwebtoken</groupId> <groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId> <artifactId>jjwt</artifactId>
</dependency> </dependency>
<!-- 不加这个报 java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter --> <!-- 不加这个报 java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter -->
<dependency> <dependency>

View File

@ -16,7 +16,7 @@
package cn.dev33.satoken.plugin; package cn.dev33.satoken.plugin;
import cn.dev33.satoken.SaManager; import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.temp.jwt.SaTempForJwt; import cn.dev33.satoken.temp.jwt.SaTempTemplateForJwt;
/** /**
* SaToken 插件安装临时 token 生成器 - Jwt * SaToken 插件安装临时 token 生成器 - Jwt
@ -28,7 +28,7 @@ public class SaTokenPluginForTempForJwt implements SaTokenPlugin {
@Override @Override
public void install() { public void install() {
SaManager.setSaTemp(new SaTempForJwt()); SaManager.setSaTempTemplate(new SaTempTemplateForJwt());
} }
} }

View File

@ -17,11 +17,14 @@ package cn.dev33.satoken.temp.jwt;
import cn.dev33.satoken.dao.SaTokenDao; import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.exception.SaTokenException; import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.secure.SaSecureUtil;
import cn.dev33.satoken.temp.jwt.error.SaTempJwtErrorCode; import cn.dev33.satoken.temp.jwt.error.SaTempJwtErrorCode;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
/** /**
* jwt 相关操作工具类封装一下 * jwt 相关操作工具类封装一下
@ -47,13 +50,12 @@ public class SaJwtUtil {
/** /**
* 根据指定值创建 jwt-token * 根据指定值创建 jwt-token
* *
* @param key 存储value使用的key
* @param value 要保存的值 * @param value 要保存的值
* @param timeout token有效期 (单位 ) * @param timeout token有效期 (单位 )
* @param keyt 秘钥 * @param keyt 秘钥
* @return jwt-token * @return jwt-token
*/ */
public static String createToken(String key, Object value, long timeout, String keyt) { public static String createToken(Object value, long timeout, String keyt) {
// 计算eff有效期 // 计算eff有效期
// 如果 timeout 指定为 -1那么 eff 也为 -1代表永不过期 // 如果 timeout 指定为 -1那么 eff 也为 -1代表永不过期
// 如果 timeout 指定为一个具体的值那么 eff 13 位时间戳代表此数据到期的时间 // 如果 timeout 指定为一个具体的值那么 eff 13 位时间戳代表此数据到期的时间
@ -62,12 +64,13 @@ public class SaJwtUtil {
eff = timeout * 1000 + System.currentTimeMillis(); eff = timeout * 1000 + System.currentTimeMillis();
} }
// 在这里你可以使用官方提供的claim方法构建载荷也可以使用setPayload自定义载荷但是两者不可一起使用 // 在这里你可以使用官方提供的claim方法构建载荷也可以使用setPayload自定义载荷但是两者不可一起使用
JwtBuilder builder = Jwts.builder() SecretKey key = Keys.hmacShaKeyFor(SaSecureUtil.md5(keyt).getBytes());
// .setHeaderParam("typ", "JWT") JwtBuilder builder = Jwts.builder()
.claim(KEY_VALUE + key, value) .header().add("typ", "JWT").and()
.claim(KEY_VALUE, value)
.claim(KEY_EFF, eff) .claim(KEY_EFF, eff)
.signWith(SignatureAlgorithm.HS256, keyt.getBytes()); .signWith(key);
// 生成jwt-token // 生成jwt-token
return builder.compact(); return builder.compact();
@ -81,19 +84,20 @@ public class SaJwtUtil {
*/ */
public static Claims parseToken(String jwtToken, String keyt) { public static Claims parseToken(String jwtToken, String keyt) {
// 解析出载荷 // 解析出载荷
SecretKey key = Keys.hmacShaKeyFor(SaSecureUtil.md5(keyt).getBytes());
return Jwts.parser() return Jwts.parser()
.setSigningKey(keyt.getBytes()) .verifyWith(key)
.parseClaimsJws(jwtToken).getBody(); .build()
.parseSignedClaims(jwtToken).getPayload();
} }
/** /**
* 从一个 jwt-token 解析出载荷, 并取出数据 * 从一个 jwt-token 解析出载荷, 并取出数据
* @param key 存储value使用的key
* @param jwtToken JwtToken值 * @param jwtToken JwtToken值
* @param keyt 秘钥 * @param keyt 秘钥
* @return * @return
*/ */
public static Object getValue(String key, String jwtToken, String keyt) { public static Object getValue(String jwtToken, String keyt) {
// 取出数据 // 取出数据
Claims claims = parseToken(jwtToken, keyt); Claims claims = parseToken(jwtToken, keyt);
@ -104,25 +108,19 @@ public class SaJwtUtil {
} }
// 获取数据 // 获取数据
return claims.get(KEY_VALUE + key); return claims.get(KEY_VALUE);
} }
/** /**
* 从一个 jwt-token 解析出载荷, 并取出其剩余有效期 * 从一个 jwt-token 解析出载荷, 并取出其剩余有效期
* @param service 指定的服务类型
* @param jwtToken JwtToken值 * @param jwtToken JwtToken值
* @param keyt 秘钥 * @param keyt 秘钥
* @return * @return
*/ */
public static long getTimeout(String service, String jwtToken, String keyt) { public static long getTimeout(String jwtToken, String keyt) {
// 取出数据 // 取出数据
Claims claims = parseToken(jwtToken, keyt); Claims claims = parseToken(jwtToken, keyt);
// 如果给定的 service 不对
if(claims.get(KEY_VALUE + service) == null) {
return SaTokenDao.NOT_VALUE_EXPIRE;
}
// 验证是否超时 // 验证是否超时
Long eff = claims.get(KEY_EFF, Long.class); Long eff = claims.get(KEY_EFF, Long.class);
@ -131,7 +129,7 @@ public class SaJwtUtil {
return NEVER_EXPIRE; return NEVER_EXPIRE;
} }
// 已经超时 // 已经超时
if(eff == null || eff < System.currentTimeMillis()) { if(eff < System.currentTimeMillis()) {
return SaTokenDao.NOT_VALUE_EXPIRE; return SaTokenDao.NOT_VALUE_EXPIRE;
} }

View File

@ -18,50 +18,61 @@ package cn.dev33.satoken.temp.jwt;
import cn.dev33.satoken.SaManager; import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.exception.ApiDisabledException; import cn.dev33.satoken.exception.ApiDisabledException;
import cn.dev33.satoken.exception.SaTokenException; import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.temp.SaTempInterface; import cn.dev33.satoken.temp.SaTempTemplate;
import cn.dev33.satoken.temp.jwt.error.SaTempJwtErrorCode; import cn.dev33.satoken.temp.jwt.error.SaTempJwtErrorCode;
import cn.dev33.satoken.util.SaFoxUtil; import cn.dev33.satoken.util.SaFoxUtil;
import java.util.List;
/** /**
* Sa-Token 临时令牌验证模块接口 JWT实现类提供以 JWT 为逻辑内核的临时 token 验证功能 * Sa-Token 临时令牌验证模块接口 JWT实现类提供以 JWT 为逻辑内核的临时 token 验证功能
* *
* @author click33 * @author click33
* @since 1.20.0 * @since 1.20.0
*/ */
public class SaTempForJwt implements SaTempInterface { public class SaTempTemplateForJwt extends SaTempTemplate {
/** /**
* 根据value创建一个token * 根据value创建一个token
*/ */
@Override @Override
public String createToken(String service, Object value, long timeout) { public String createToken(Object value, long timeout, boolean isRecordIndex) {
return SaJwtUtil.createToken(service, value, timeout, getJwtSecretKey()); return SaJwtUtil.createToken(value, timeout, getJwtSecretKey());
} }
/** /**
* 解析token获取value * 解析token获取value
*/ */
@Override @Override
public Object parseToken(String service, String token) { public Object parseToken(String token) {
return SaJwtUtil.getValue(service, token, getJwtSecretKey()); return SaJwtUtil.getValue(token, getJwtSecretKey());
} }
/** /**
* 返回指定token的剩余有效期单位 * 返回指定token的剩余有效期单位
*/ */
@Override @Override
public long getTimeout(String service, String token) { public long getTimeout(String token) {
return SaJwtUtil.getTimeout(service, token, getJwtSecretKey()); return SaJwtUtil.getTimeout(token, getJwtSecretKey());
} }
/** /**
* 删除一个token * 删除一个token
*/ */
@Override @Override
public void deleteToken(String service, String token) { public void deleteToken(String token) {
throw new ApiDisabledException("jwt cannot delete token").setCode(SaTempJwtErrorCode.CODE_30302); throw new ApiDisabledException("jwt cannot delete token").setCode(SaTempJwtErrorCode.CODE_30302);
} }
/**
* 获取指定 value temp-token 列表记录
* @param value /
* @return /
*/
public List<String> getTempTokenList(Object value) {
throw new ApiDisabledException("jwt cannot get token list").setCode(SaTempJwtErrorCode.CODE_30304);
}
/** /**
* 获取jwt秘钥 * 获取jwt秘钥
* @return jwt秘钥 * @return jwt秘钥

View File

@ -32,4 +32,7 @@ public interface SaTempJwtErrorCode {
/** Token已超时 */ /** Token已超时 */
int CODE_30303 = 30303; int CODE_30303 = 30303;
/** jwt 模式不可以查询旧 Token 列表 */
int CODE_30304 = 30304;
} }

View File

@ -42,7 +42,7 @@ import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.strategy.SaAnnotationStrategy; import cn.dev33.satoken.strategy.SaAnnotationStrategy;
import cn.dev33.satoken.strategy.SaFirewallStrategy; import cn.dev33.satoken.strategy.SaFirewallStrategy;
import cn.dev33.satoken.strategy.hooks.SaFirewallCheckHook; import cn.dev33.satoken.strategy.hooks.SaFirewallCheckHook;
import cn.dev33.satoken.temp.SaTempInterface; import cn.dev33.satoken.temp.SaTempTemplate;
import org.noear.solon.annotation.Bean; import org.noear.solon.annotation.Bean;
import org.noear.solon.annotation.Condition; import org.noear.solon.annotation.Condition;
import org.noear.solon.annotation.Configuration; import org.noear.solon.annotation.Configuration;
@ -145,12 +145,12 @@ public class SaBeanInject {
/** /**
* 注入临时令牌验证模块 Bean * 注入临时令牌验证模块 Bean
* *
* @param saTemp saTemp对象 * @param saTempTemplate /
*/ */
@Condition(onBean = SaTempInterface.class) @Condition(onBean = SaTempTemplate.class)
@Bean @Bean
public void setSaTemp(SaTempInterface saTemp) { public void setSaTempTemplate(SaTempTemplate saTempTemplate) {
SaManager.setSaTemp(saTemp); SaManager.setSaTempTemplate(saTempTemplate);
} }
/** /**

View File

@ -43,7 +43,7 @@ import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.strategy.SaAnnotationStrategy; import cn.dev33.satoken.strategy.SaAnnotationStrategy;
import cn.dev33.satoken.strategy.SaFirewallStrategy; import cn.dev33.satoken.strategy.SaFirewallStrategy;
import cn.dev33.satoken.strategy.hooks.SaFirewallCheckHook; import cn.dev33.satoken.strategy.hooks.SaFirewallCheckHook;
import cn.dev33.satoken.temp.SaTempInterface; import cn.dev33.satoken.temp.SaTempTemplate;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.util.PathMatcher; import org.springframework.util.PathMatcher;
@ -139,11 +139,11 @@ public class SaBeanInject {
/** /**
* 注入临时令牌验证模块 Bean * 注入临时令牌验证模块 Bean
* *
* @param saTemp saTemp对象 * @param saTempTemplate /
*/ */
@Autowired(required = false) @Autowired(required = false)
public void setSaTemp(SaTempInterface saTemp) { public void setSaTempTemplate(SaTempTemplate saTempTemplate) {
SaManager.setSaTemp(saTemp); SaManager.setSaTempTemplate(saTempTemplate);
} }
/** /**

View File

@ -24,6 +24,7 @@
<module>sa-token-springboot-test</module> <module>sa-token-springboot-test</module>
<!-- <module>sa-token-springboot-integrate-test</module> --> <!-- <module>sa-token-springboot-integrate-test</module> -->
<module>sa-token-jwt-test</module> <module>sa-token-jwt-test</module>
<module>sa-token-temp-jwt-test</module>
<module>sa-token-json-test</module> <module>sa-token-json-test</module>
<module>sa-token-serializer-test</module> <module>sa-token-serializer-test</module>
</modules> </modules>

View File

@ -1,112 +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.core.temp;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.temp.SaTempUtil;
import cn.dev33.satoken.util.SaTokenConsts;
/**
* 临时Token模块测试
*
* @author click33
* @since 2022-9-1
*/
public class SaTempTest {
// 测试临时Token认证模块
@Test
public void testSaTemp() {
SaTokenDao dao = SaManager.getSaTokenDao();
// 生成token
String token = SaTempUtil.createToken("group-1014", 200);
Assertions.assertNotNull(token);
// System.out.println(((SaTokenDaoDefaultImpl)SaManager.getSaTokenDao()).dataMap);
// 解析token
String value = SaTempUtil.parseToken(token, String.class);
Assertions.assertEquals(value, "group-1014");
Assertions.assertEquals(dao.getObject("satoken:temp-token:" + SaTokenConsts.DEFAULT_TEMP_TOKEN_SERVICE + ":" + token), "group-1014");
// 默认类型
Object value3 = SaTempUtil.parseToken(token);
Assertions.assertEquals(value3, "group-1014");
// 转换类型
String value4 = SaTempUtil.parseToken(token, String.class);
Assertions.assertEquals(value4, "group-1014");
// 过期时间
long timeout = SaTempUtil.getTimeout(token);
Assertions.assertTrue(timeout > 195);
// 回收token
SaTempUtil.deleteToken(token);
String value2 = SaTempUtil.parseToken(token, String.class);
Assertions.assertEquals(value2, null);
Assertions.assertEquals(dao.getObject("satoken:temp-token:" + SaTokenConsts.DEFAULT_TEMP_TOKEN_SERVICE + ":" + token), null);
}
// 测试临时Token认证模块 Service 参数
@Test
public void testSaTempService() {
SaTokenDao dao = SaManager.getSaTokenDao();
// 生成token
String token = SaTempUtil.createToken("shop", "1001", 200);
Assertions.assertNotNull(token);
// System.out.println(((SaTokenDaoDefaultImpl)SaManager.getSaTokenDao()).dataMap);
// 解析token
String value = SaTempUtil.parseToken("shop", token, String.class);
Assertions.assertEquals(value, "1001");
Assertions.assertEquals(dao.getObject("satoken:temp-token:" + "shop" + ":" + token), "1001");
// 默认类型
Object value3 = SaTempUtil.parseToken("shop", token);
Assertions.assertEquals(value3, "1001");
// 转换类型
String value4 = SaTempUtil.parseToken("shop", token, String.class);
Assertions.assertEquals(value4, "1001");
// service 参数不对的情况下无法取出
String value5 = SaTempUtil.parseToken("goods", token, String.class);
Assertions.assertNull(value5);
// 过期时间
long timeout = SaTempUtil.getTimeout("shop", token);
Assertions.assertTrue(timeout > 195);
// 回收token
SaTempUtil.deleteToken("shop", token);
String value2 = SaTempUtil.parseToken("shop", token, String.class);
Assertions.assertEquals(value2, null);
Assertions.assertEquals(dao.getObject("satoken:temp-token:" + "shop" + ":" + token), null);
}
@Test
public void testSaTemp2() {
// 秘钥默认为null
String jwtSecretKey = SaManager.getSaTemp().getJwtSecretKey();
Assertions.assertEquals(jwtSecretKey, null);
}
}

View File

@ -0,0 +1,142 @@
/*
* 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.core.temp;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.temp.SaTempUtil;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.List;
/**
* 临时Token模块测试
*
* @author click33
* @since 2022-9-1
*/
public class SaTempTokenTest {
// 测试临时Token认证模块
@Test
public void testSaTemp() {
SaTokenDao dao = SaManager.getSaTokenDao();
// 生成token
String token = SaTempUtil.createToken("group-1014", 200);
// System.out.println(((SaTokenDaoDefaultImpl)SaManager.getSaTokenDao()).timedCache.dataMap.keySet());
// System.out.println("satoken:temp-token:" + ":" + token);
Assertions.assertNotNull(token);
Assertions.assertEquals(dao.getObject("satoken:temp-token:" + token), "group-1014");
// 解析token
String value = SaTempUtil.parseToken(token, String.class);
Assertions.assertEquals(value, "group-1014");
// 解析 token 并裁剪前缀
long value2 = SaTempUtil.parseToken(token, "group-", Long.class);
Assertions.assertEquals(value2, 1014);
// 默认类型
Object value3 = SaTempUtil.parseToken(token);
Assertions.assertEquals(value3, "group-1014");
// 转换类型
String value4 = SaTempUtil.parseToken(token, String.class);
Assertions.assertEquals(value4, "group-1014");
// 过期时间
long timeout = SaTempUtil.getTimeout(token);
Assertions.assertTrue(timeout > 195);
Assertions.assertTrue(timeout < 201);
// 回收token
SaTempUtil.deleteToken(token);
String value5 = SaTempUtil.parseToken(token, String.class);
Assertions.assertNull(value5);
Assertions.assertNull(dao.getObject("satoken:temp-token:" + ":" + token));
}
// 测试临时Token认证模块索引
@Test
public void testSaTempIndex() {
SaTokenDao dao = SaManager.getSaTokenDao();
// 生成token
String token1 = SaTempUtil.createToken("1001", 200, true);
String token2 = SaTempUtil.createToken("1001", 300, true);
String token3 = SaTempUtil.createToken("1001", 400, true);
Assertions.assertNotNull(token1);
Assertions.assertNotNull(token2);
Assertions.assertNotNull(token3);
// System.out.println(((SaTokenDaoDefaultImpl)SaManager.getSaTokenDao()).dataMap);
// 解析token
Assertions.assertEquals(SaTempUtil.parseToken(token1, String.class), "1001");
Assertions.assertEquals(SaTempUtil.parseToken(token2, String.class), "1001");
Assertions.assertEquals(SaTempUtil.parseToken(token3, String.class), "1001");
// 缓存数据比对
Assertions.assertEquals(dao.getObject("satoken:temp-token:" + token1), "1001");
Assertions.assertEquals(dao.getObject("satoken:temp-token:" + token2), "1001");
Assertions.assertEquals(dao.getObject("satoken:temp-token:" + token3), "1001");
// 索引
List<String> tempTokenList = SaTempUtil.getTempTokenList("1001");
Assertions.assertEquals(tempTokenList.size(), 3);
Assertions.assertTrue(tempTokenList.contains(token1));
Assertions.assertTrue(tempTokenList.contains(token2));
Assertions.assertTrue(tempTokenList.contains(token3));
long sessionTimeout = dao.getSessionTimeout("satoken:raw-session:temp-token:" + "1001");
Assertions.assertTrue(sessionTimeout > 395);
Assertions.assertTrue(sessionTimeout < 401);
// 移除一个 token
SaTempUtil.deleteToken(token3);
Assertions.assertNull(SaTempUtil.parseToken(token3, String.class));
Assertions.assertNull(dao.getObject("satoken:temp-token:" + token3));
List<String> tempTokenList2 = SaTempUtil.getTempTokenList("1001");
Assertions.assertEquals(tempTokenList2.size(), 2);
Assertions.assertFalse(tempTokenList2.contains(token3));
long sessionTimeout2 = dao.getSessionTimeout("satoken:raw-session:temp-token:" + "1001");
Assertions.assertTrue(sessionTimeout2 > 295);
Assertions.assertTrue(sessionTimeout2 < 301);
// 新增一个 token
String token4 = SaTempUtil.createToken("1001", -1, true);
Assertions.assertEquals(SaTempUtil.parseToken(token4, String.class), "1001");
List<String> tempTokenList3 = SaTempUtil.getTempTokenList("1001");
Assertions.assertEquals(tempTokenList3.size(), 3);
Assertions.assertTrue(tempTokenList3.contains(token4));
long sessionTimeout4 = dao.getSessionTimeout("satoken:raw-session:temp-token:" + "1001");
Assertions.assertEquals(-1, sessionTimeout4);
}
@Test
public void testGetJwtSecretKey() {
// 秘钥默认为null
String jwtSecretKey = SaManager.getSaTempTemplate().getJwtSecretKey();
Assertions.assertNull(jwtSecretKey);
}
}

View File

@ -15,11 +15,10 @@
*/ */
package cn.dev33.satoken.integrate.configure.inject; package cn.dev33.satoken.integrate.configure.inject;
import cn.dev33.satoken.temp.SaTempTemplate;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import cn.dev33.satoken.temp.SaTempDefaultImpl;
@Component @Component
public class MySaTemp extends SaTempDefaultImpl { public class MySaTempTemplate extends SaTempTemplate {
} }

View File

@ -0,0 +1,30 @@
<?xml version='1.0' encoding='utf-8'?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-test</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<packaging>jar</packaging>
<name>sa-token-temp-jwt-test</name>
<artifactId>sa-token-temp-jwt-test</artifactId>
<description>sa-token-temp-jwt-test</description>
<dependencies>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-temp-jwt</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,105 @@
package com.pj.test;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.exception.ApiDisabledException;
import cn.dev33.satoken.temp.SaTempUtil;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
/**
* Sa-Token 整合 temp jwt
*
* @author click33
* @since 1.42.0
*/
@SpringBootTest(classes = StartUpApplication.class)
public class SaTempTemplateForJwtTest {
// 开始
@BeforeAll
public static void beforeClass() {
System.out.println("\n\n------------------------ SaTempTemplateForJwtTest star ...");
}
// 结束
@AfterAll
public static void afterClass() {
System.out.println("\n\n------------------------ SaTempTemplateForJwtTest end ... \n");
}
// 测试临时Token认证模块
@Test
public void testSaTemp() {
// 生成token
String token = SaTempUtil.createToken("group-1014", 200);
// System.out.println(((SaTokenDaoDefaultImpl)SaManager.getSaTokenDao()).timedCache.dataMap.keySet());
// System.out.println("satoken:temp-token:" + ":" + token);
Assertions.assertNotNull(token);
// 解析token
String value = SaTempUtil.parseToken(token, String.class);
Assertions.assertEquals(value, "group-1014");
// 解析 token 并裁剪前缀
long value2 = SaTempUtil.parseToken(token, "group-", Long.class);
Assertions.assertEquals(value2, 1014);
// 默认类型
Object value3 = SaTempUtil.parseToken(token);
Assertions.assertEquals(value3, "group-1014");
// 转换类型
String value4 = SaTempUtil.parseToken(token, String.class);
Assertions.assertEquals(value4, "group-1014");
// 过期时间
long timeout = SaTempUtil.getTimeout(token);
Assertions.assertTrue(timeout > 195);
Assertions.assertTrue(timeout < 201);
// 回收token
Assertions.assertThrows(ApiDisabledException.class, () -> SaTempUtil.deleteToken(token) );
}
// 测试临时Token认证模块索引
@Test
public void testSaTempIndex() {
SaTokenDao dao = SaManager.getSaTokenDao();
// 生成token
String token1 = SaTempUtil.createToken("1001", 200, true);
String token2 = SaTempUtil.createToken("1001", 300, true);
String token3 = SaTempUtil.createToken("1001", 400, true);
Assertions.assertNotNull(token1);
Assertions.assertNotNull(token2);
Assertions.assertNotNull(token3);
// System.out.println(((SaTokenDaoDefaultImpl)SaManager.getSaTokenDao()).dataMap);
// 解析token
Assertions.assertEquals(SaTempUtil.parseToken(token1, String.class), "1001");
Assertions.assertEquals(SaTempUtil.parseToken(token2, String.class), "1001");
Assertions.assertEquals(SaTempUtil.parseToken(token3, String.class), "1001");
// 缓存数据比对
Assertions.assertNull(dao.getObject("satoken:temp-token:" + token1));
Assertions.assertNull(dao.getObject("satoken:temp-token:" + token2));
Assertions.assertNull(dao.getObject("satoken:temp-token:" + token3));
// 索引
Assertions.assertThrows(ApiDisabledException.class, () -> SaTempUtil.getTempTokenList("1001") );
}
@Test
public void testGetJwtSecretKey() {
// 秘钥默认为null
String jwtSecretKey = SaManager.getSaTempTemplate().getJwtSecretKey();
Assertions.assertNotNull(jwtSecretKey);
}
}

View File

@ -0,0 +1,16 @@
package com.pj.test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 启动类
* @author Auster
*
*/
@SpringBootApplication
public class StartUpApplication {
public static void main(String[] args) {
SpringApplication.run(StartUpApplication.class, args);
}
}

View File

@ -0,0 +1,46 @@
# 端口
server:
port: 8081
# sa-token 配置
sa-token:
# token 名称 (同时也是 cookie 名称)
token-name: satoken
# token 有效期(单位:秒) 默认30天-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: -1
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token
is-share: false
# token 风格默认可取值uuid、simple-uuid、random-32、random-64、random-128、tik
token-style: uuid
# jwt秘钥
jwt-secret-key: asdasdasifhueuiwyurfewbfjsdafjk
spring:
# redis配置
redis:
# Redis数据库索引默认为0
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码默认为空
password:
# 连接超时时间(毫秒)
timeout: 10000ms
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0