mirror of
https://gitee.com/dromara/sa-token.git
synced 2026-02-27 16:50:24 +08:00
feat: 新增 API Key 模块
This commit is contained in:
@@ -15,6 +15,9 @@
|
||||
*/
|
||||
package cn.dev33.satoken;
|
||||
|
||||
import cn.dev33.satoken.apikey.SaApiKeyTemplate;
|
||||
import cn.dev33.satoken.apikey.loader.SaApiKeyDataLoader;
|
||||
import cn.dev33.satoken.apikey.loader.SaApiKeyDataLoaderDefaultImpl;
|
||||
import cn.dev33.satoken.config.SaTokenConfig;
|
||||
import cn.dev33.satoken.config.SaTokenConfigFactory;
|
||||
import cn.dev33.satoken.context.SaTokenContext;
|
||||
@@ -317,6 +320,47 @@ public class SaManager {
|
||||
return totpTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* ApiKey 数据加载器
|
||||
*/
|
||||
private volatile static SaApiKeyDataLoader apiKeyDataLoader;
|
||||
public static void setSaApiKeyDataLoader(SaApiKeyDataLoader apiKeyDataLoader) {
|
||||
SaManager.apiKeyDataLoader = apiKeyDataLoader;
|
||||
SaTokenEventCenter.doRegisterComponent("SaApiKeyDataLoader", apiKeyDataLoader);
|
||||
}
|
||||
public static SaApiKeyDataLoader getSaApiKeyDataLoader() {
|
||||
if (apiKeyDataLoader == null) {
|
||||
synchronized (SaManager.class) {
|
||||
if (apiKeyDataLoader == null) {
|
||||
SaManager.apiKeyDataLoader = new SaApiKeyDataLoaderDefaultImpl();
|
||||
}
|
||||
}
|
||||
}
|
||||
return apiKeyDataLoader;
|
||||
}
|
||||
|
||||
/**
|
||||
* ApiKey 操作类
|
||||
*/
|
||||
private volatile static SaApiKeyTemplate apiKeyTemplate;
|
||||
public static void setSaApiKeyTemplate(SaApiKeyTemplate apiKeyTemplate) {
|
||||
SaManager.apiKeyTemplate = apiKeyTemplate;
|
||||
SaTokenEventCenter.doRegisterComponent("SaApiKeyTemplate", apiKeyTemplate);
|
||||
}
|
||||
public static SaApiKeyTemplate getSaApiKeyTemplate() {
|
||||
if (apiKeyTemplate == null) {
|
||||
synchronized (SaManager.class) {
|
||||
if (apiKeyTemplate == null) {
|
||||
SaManager.apiKeyTemplate = new SaApiKeyTemplate();
|
||||
}
|
||||
}
|
||||
}
|
||||
return apiKeyTemplate;
|
||||
}
|
||||
|
||||
|
||||
// ------------------- StpLogic 相关 -------------------
|
||||
|
||||
/**
|
||||
* StpLogic 集合, 记录框架所有成功初始化的 StpLogic
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* API Key 校验:指定请求中必须包含有效的 ApiKey ,并且包含指定的 scope
|
||||
*
|
||||
* <p> 可标注在方法、类上(效果等同于标注在此类的所有方法上)
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD,ElementType.TYPE})
|
||||
public @interface SaCheckApiKey {
|
||||
|
||||
/**
|
||||
* 指定 API key 必须包含的权限 [ 数组 ]
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
String [] scope() default {};
|
||||
|
||||
/**
|
||||
* 验证模式:AND | OR,默认AND
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
SaMode mode() default SaMode.AND;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.annotation.handler;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckApiKey;
|
||||
import cn.dev33.satoken.annotation.SaMode;
|
||||
import cn.dev33.satoken.apikey.SaApiKeyUtil;
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 注解 SaCheckApiKey 的处理器
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class SaCheckApiKeyHandler implements SaAnnotationHandlerInterface<SaCheckApiKey> {
|
||||
|
||||
@Override
|
||||
public Class<SaCheckApiKey> getHandlerAnnotationClass() {
|
||||
return SaCheckApiKey.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkMethod(SaCheckApiKey at, Method method) {
|
||||
_checkMethod(at.scope(), at.mode());
|
||||
}
|
||||
|
||||
public static void _checkMethod(String[] scope, SaMode mode) {
|
||||
String apiKey = SaApiKeyUtil.readApiKeyValue(SaHolder.getRequest());
|
||||
if(mode == SaMode.AND) {
|
||||
SaApiKeyUtil.checkApiKeyScope(apiKey, scope);
|
||||
} else {
|
||||
SaApiKeyUtil.checkApiKeyScopeOr(apiKey, scope);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,537 @@
|
||||
/*
|
||||
* 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.apikey;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.apikey.model.ApiKeyModel;
|
||||
import cn.dev33.satoken.config.SaTokenConfig;
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.context.model.SaRequest;
|
||||
import cn.dev33.satoken.dao.SaTokenDao;
|
||||
import cn.dev33.satoken.error.SaErrorCode;
|
||||
import cn.dev33.satoken.exception.ApiKeyException;
|
||||
import cn.dev33.satoken.exception.ApiKeyScopeException;
|
||||
import cn.dev33.satoken.httpauth.basic.SaHttpBasicUtil;
|
||||
import cn.dev33.satoken.session.SaSession;
|
||||
import cn.dev33.satoken.session.SaSessionRawUtil;
|
||||
import cn.dev33.satoken.strategy.SaStrategy;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* API Key 操作类
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class SaApiKeyTemplate {
|
||||
|
||||
/**
|
||||
* ApiKey 的 raw-session 类型
|
||||
*/
|
||||
public static final String SESSION_TYPE = "apikey";
|
||||
|
||||
/**
|
||||
* 在 raw-session 中的保存索引列表使用的 key
|
||||
*/
|
||||
public static final String API_KEY_LIST = "__HD_API_KEY_LIST";
|
||||
|
||||
/**
|
||||
* 网络传输时的参数名称 (字母全小写)
|
||||
*/
|
||||
public static final String API_KEY_PARAMETER_NAME = "apikey";
|
||||
|
||||
// ------------------- ApiKey
|
||||
|
||||
/**
|
||||
* 根据 apiKey 从 Cache 获取 ApiKeyModel 信息
|
||||
* @param apiKey /
|
||||
* @return /
|
||||
*/
|
||||
public ApiKeyModel getApiKeyModelFromCache(String apiKey) {
|
||||
return getSaTokenDao().getObject(splicingApiKeySaveKey(apiKey), ApiKeyModel.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 apiKey 从 Database 获取 ApiKeyModel 信息
|
||||
* @param apiKey /
|
||||
* @return /
|
||||
*/
|
||||
public ApiKeyModel getApiKeyModelFromDatabase(String apiKey) {
|
||||
return SaManager.getSaApiKeyDataLoader().getApiKeyModelFromDatabase(apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 ApiKeyModel,无效的 ApiKey 会返回 null
|
||||
* @param apiKey /
|
||||
* @return /
|
||||
*/
|
||||
public ApiKeyModel getApiKey(String apiKey) {
|
||||
if(apiKey == null) {
|
||||
return null;
|
||||
}
|
||||
// 先从缓存中获取,缓存中找不到就尝试从数据库获取
|
||||
ApiKeyModel apiKeyModel = getApiKeyModelFromCache(apiKey);
|
||||
if(apiKeyModel == null) {
|
||||
apiKeyModel = getApiKeyModelFromDatabase(apiKey);
|
||||
saveApiKey(apiKeyModel);
|
||||
}
|
||||
return apiKeyModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验 ApiKey,成功返回 ApiKeyModel,失败则抛出异常
|
||||
* @param apiKey /
|
||||
* @return /
|
||||
*/
|
||||
public ApiKeyModel checkApiKey(String apiKey) {
|
||||
ApiKeyModel ak = getApiKey(apiKey);
|
||||
if(ak == null) {
|
||||
throw new ApiKeyException("无效 API Key: " + apiKey).setApiKey(apiKey).setCode(SaErrorCode.CODE_12301);
|
||||
}
|
||||
if(ak.timeExpired()) {
|
||||
throw new ApiKeyException("API Key 已过期: " + apiKey).setApiKey(apiKey).setCode(SaErrorCode.CODE_12302);
|
||||
}
|
||||
if(! ak.getIsValid()) {
|
||||
throw new ApiKeyException("API Key 已被禁用: " + apiKey).setApiKey(apiKey).setCode(SaErrorCode.CODE_12303);
|
||||
}
|
||||
return ak;
|
||||
}
|
||||
|
||||
/**
|
||||
* 持久化:ApiKeyModel
|
||||
* @param ak /
|
||||
*/
|
||||
public void saveApiKey(ApiKeyModel ak) {
|
||||
if(ak == null) {
|
||||
return;
|
||||
}
|
||||
// 数据自检
|
||||
ak.checkByCanSaved();
|
||||
|
||||
// 保存 ApiKeyModel
|
||||
String saveKey = splicingApiKeySaveKey(ak.getApiKey());
|
||||
if(ak.timeExpired()) {
|
||||
getSaTokenDao().deleteObject(saveKey);
|
||||
} else {
|
||||
getSaTokenDao().setObject(saveKey, ak, ak.expiresIn());
|
||||
}
|
||||
|
||||
// 调整索引
|
||||
if (SaManager.getSaApiKeyDataLoader().getIsRecordIndex()) {
|
||||
// 记录索引
|
||||
SaSession session = SaSessionRawUtil.getSessionById(SESSION_TYPE, ak.getLoginId());
|
||||
ArrayList<String> apiKeyList = session.get(API_KEY_LIST, ArrayList::new);
|
||||
if(! apiKeyList.contains(ak.getApiKey())) {
|
||||
apiKeyList.add(ak.getApiKey());
|
||||
session.set(API_KEY_LIST, apiKeyList);
|
||||
}
|
||||
|
||||
// 调整 ttl
|
||||
adjustIndex(ak.getLoginId(), session);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 ApiKey 所代表的 LoginId
|
||||
* @param apiKey ApiKey
|
||||
* @return LoginId
|
||||
*/
|
||||
public Object getLoginIdByApiKey(String apiKey) {
|
||||
return checkApiKey(apiKey).getLoginId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 ApiKey
|
||||
* @param apiKey ApiKey
|
||||
*/
|
||||
public void deleteApiKey(String apiKey) {
|
||||
// 删 ApiKeyModel
|
||||
ApiKeyModel ak = getApiKeyModelFromCache(apiKey);
|
||||
if(ak == null) {
|
||||
return;
|
||||
}
|
||||
getSaTokenDao().deleteObject(splicingApiKeySaveKey(apiKey));
|
||||
|
||||
// 删索引
|
||||
if(SaManager.getSaApiKeyDataLoader().getIsRecordIndex()) {
|
||||
// RawSession 中不存在,提前退出
|
||||
SaSession session = SaSessionRawUtil.getSessionById(SESSION_TYPE, ak.getLoginId(), false);
|
||||
if(session == null) {
|
||||
return;
|
||||
}
|
||||
// 索引无记录,提前退出
|
||||
ArrayList<String> apiKeyList = session.get(API_KEY_LIST, ArrayList::new);
|
||||
if(! apiKeyList.contains(apiKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果只有一个 ApiKey,则整个 RawSession 删掉
|
||||
if (apiKeyList.size() == 1) {
|
||||
SaSessionRawUtil.deleteSessionById(SESSION_TYPE, ak.getLoginId());
|
||||
} else {
|
||||
// 否则移除此 ApiKey 并保存
|
||||
apiKeyList.remove(apiKey);
|
||||
session.set(API_KEY_LIST, apiKeyList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定 loginId 的所有 ApiKey
|
||||
* @param loginId /
|
||||
*/
|
||||
public void deleteApiKeyByLoginId(Object loginId) {
|
||||
// 先判断是否开启索引
|
||||
if(! SaManager.getSaApiKeyDataLoader().getIsRecordIndex()) {
|
||||
SaManager.getLog().warn("当前 API Key 模块未开启索引记录功能,无法执行 deleteApiKeyByLoginId 操作");
|
||||
return;
|
||||
}
|
||||
|
||||
// RawSession 中不存在,提前退出
|
||||
SaSession session = SaSessionRawUtil.getSessionById(SESSION_TYPE, loginId, false);
|
||||
if(session == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 先删 ApiKeyModel
|
||||
ArrayList<String> apiKeyList = session.get(API_KEY_LIST, ArrayList::new);
|
||||
for (String apiKey : apiKeyList) {
|
||||
getSaTokenDao().deleteObject(splicingApiKeySaveKey(apiKey));
|
||||
}
|
||||
|
||||
// 再删索引
|
||||
SaSessionRawUtil.deleteSessionById(SESSION_TYPE, loginId);
|
||||
}
|
||||
|
||||
// ------- 创建
|
||||
|
||||
/**
|
||||
* 创建一个 ApiKeyModel 对象
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public ApiKeyModel createApiKeyModel() {
|
||||
String apiKey = SaStrategy.instance.generateUniqueToken.execute(
|
||||
"API Key",
|
||||
SaManager.getConfig().getMaxTryTimes(),
|
||||
this::randomApiKeyValue,
|
||||
_apiKey -> getApiKey(_apiKey) == null
|
||||
);
|
||||
return new ApiKeyModel().setApiKey(apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个 ApiKeyModel 对象
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public ApiKeyModel createApiKeyModel(Object loginId) {
|
||||
long timeout = SaManager.getConfig().getApiKey().getTimeout();
|
||||
long expiresTime = (timeout == SaTokenDao.NEVER_EXPIRE) ? SaTokenDao.NEVER_EXPIRE : System.currentTimeMillis() + timeout * 1000;
|
||||
return createApiKeyModel()
|
||||
.setLoginId(loginId)
|
||||
.setIsValid(true)
|
||||
.setExpiresTime(expiresTime)
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机一个 ApiKey 码
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public String randomApiKeyValue() {
|
||||
return SaManager.getConfig().getApiKey().getPrefix() + SaFoxUtil.getRandomString(36);
|
||||
}
|
||||
|
||||
|
||||
// ------------------- 校验
|
||||
|
||||
/**
|
||||
* 判断:指定 ApiKey 是否具有指定 Scope 列表 (AND 模式,需要全部具备),返回 true 或 false
|
||||
* @param apiKey ApiKey
|
||||
* @param scopes 需要校验的权限列表
|
||||
*/
|
||||
public boolean hasApiKeyScope(String apiKey, String... scopes) {
|
||||
try {
|
||||
checkApiKeyScope(apiKey, scopes);
|
||||
return true;
|
||||
} catch (ApiKeyException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验:指定 ApiKey 是否具有指定 Scope 列表 (AND 模式,需要全部具备),如果不具备则抛出异常
|
||||
* @param apiKey ApiKey
|
||||
* @param scopes 需要校验的权限列表
|
||||
*/
|
||||
public void checkApiKeyScope(String apiKey, String... scopes) {
|
||||
ApiKeyModel ak = checkApiKey(apiKey);
|
||||
if(SaFoxUtil.isEmptyArray(scopes)) {
|
||||
return;
|
||||
}
|
||||
for (String scope : scopes) {
|
||||
if(! ak.getScopes().contains(scope)) {
|
||||
throw new ApiKeyScopeException("该 API Key 不具备 Scope:" + scope)
|
||||
.setApiKey(apiKey)
|
||||
.setScope(scope)
|
||||
.setCode(SaErrorCode.CODE_12311);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断:指定 ApiKey 是否具有指定 Scope 列表 (OR 模式,具备其一即可),返回 true 或 false
|
||||
* @param apiKey ApiKey
|
||||
* @param scopes 需要校验的权限列表
|
||||
*/
|
||||
public boolean hasApiKeyScopeOr(String apiKey, String... scopes) {
|
||||
try {
|
||||
checkApiKeyScopeOr(apiKey, scopes);
|
||||
return true;
|
||||
} catch (ApiKeyException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验:指定 ApiKey 是否具有指定 Scope 列表 (OR 模式,具备其一即可),如果不具备则抛出异常
|
||||
* @param apiKey ApiKey
|
||||
* @param scopes 需要校验的权限列表
|
||||
*/
|
||||
public void checkApiKeyScopeOr(String apiKey, String... scopes) {
|
||||
ApiKeyModel ak = checkApiKey(apiKey);
|
||||
if(SaFoxUtil.isEmptyArray(scopes)) {
|
||||
return;
|
||||
}
|
||||
for (String scope : scopes) {
|
||||
if(ak.getScopes().contains(scope)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new ApiKeyScopeException("该 API Key 不具备 Scope:" + scopes[0])
|
||||
.setApiKey(apiKey)
|
||||
.setScope(scopes[0])
|
||||
.setCode(SaErrorCode.CODE_12311);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断:指定 ApiKey 是否属于指定 LoginId,返回 true 或 false
|
||||
* @param apiKey /
|
||||
* @param loginId /
|
||||
*/
|
||||
public boolean isApiKeyLoginId(String apiKey, Object loginId) {
|
||||
try {
|
||||
checkApiKeyLoginId(apiKey, loginId);
|
||||
return true;
|
||||
} catch (ApiKeyException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验:指定 ApiKey 是否属于指定 LoginId,如果不是则抛出异常
|
||||
*
|
||||
* @param apiKey /
|
||||
* @param loginId /
|
||||
*/
|
||||
public void checkApiKeyLoginId(String apiKey, Object loginId) {
|
||||
ApiKeyModel ak = getApiKey(apiKey);
|
||||
if(ak == null) {
|
||||
throw new ApiKeyException("无效 API Key: " + apiKey).setApiKey(apiKey).setCode(SaErrorCode.CODE_12301);
|
||||
}
|
||||
if (SaFoxUtil.notEquals(String.valueOf(ak.getLoginId()), String.valueOf(loginId))) {
|
||||
throw new ApiKeyException("该 API Key 不属于用户: " + loginId)
|
||||
.setApiKey(apiKey)
|
||||
.setCode(SaErrorCode.CODE_12312);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ------------------- 索引操作
|
||||
|
||||
/**
|
||||
* 调整指定 SaSession 的 TTL 值,以保证最小化内存占用
|
||||
* @param loginId /
|
||||
* @param session 可填写 null,代表使用 loginId 现场查询
|
||||
*/
|
||||
public void adjustIndex(Object loginId, SaSession session) {
|
||||
// 先判断是否开启索引
|
||||
if(! SaManager.getSaApiKeyDataLoader().getIsRecordIndex()) {
|
||||
SaManager.getLog().warn("当前 API Key 模块未开启索引记录功能,无法执行 adjustIndex 操作");
|
||||
return;
|
||||
}
|
||||
|
||||
// 未提供则现场查询
|
||||
if(session == null) {
|
||||
session = SaSessionRawUtil.getSessionById(SESSION_TYPE, loginId, false);
|
||||
if(session == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 重新整理索引列表
|
||||
ArrayList<String> apiKeyList = session.get(API_KEY_LIST, ArrayList::new);
|
||||
ArrayList<String> apiKeyNewList = new ArrayList<>();
|
||||
ArrayList<ApiKeyModel> apiKeyModelList = new ArrayList<>();
|
||||
for (String apikey : apiKeyList) {
|
||||
ApiKeyModel ak = getApiKeyModelFromCache(apikey);
|
||||
if(ak == null || ak.timeExpired()) {
|
||||
continue;
|
||||
}
|
||||
apiKeyNewList.add(apikey);
|
||||
apiKeyModelList.add(ak);
|
||||
}
|
||||
session.set(API_KEY_LIST, apiKeyNewList);
|
||||
|
||||
// 调整 SaSession TTL
|
||||
long maxTtl = 0;
|
||||
for (ApiKeyModel ak : apiKeyModelList) {
|
||||
long ttl = ak.expiresIn();
|
||||
if(ttl == SaTokenDao.NEVER_EXPIRE) {
|
||||
maxTtl = SaTokenDao.NEVER_EXPIRE;
|
||||
break;
|
||||
}
|
||||
if(ttl > maxTtl) {
|
||||
maxTtl = ttl;
|
||||
}
|
||||
}
|
||||
if(maxTtl != 0) {
|
||||
session.updateTimeout(maxTtl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定 loginId 的 ApiKey 列表记录
|
||||
* @param loginId /
|
||||
* @return /
|
||||
*/
|
||||
public List<ApiKeyModel> getApiKeyList(Object loginId) {
|
||||
// 先判断是否开启索引
|
||||
if(! SaManager.getSaApiKeyDataLoader().getIsRecordIndex()) {
|
||||
SaManager.getLog().warn("当前 API Key 模块未开启索引记录功能,无法执行 getApiKeyList 操作");
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
// 先查 RawSession
|
||||
List<ApiKeyModel> apiKeyModelList = new ArrayList<>();
|
||||
SaSession session = SaSessionRawUtil.getSessionById(SESSION_TYPE, loginId, false);
|
||||
if(session == null) {
|
||||
return apiKeyModelList;
|
||||
}
|
||||
|
||||
// 从 RawSession 遍历查询
|
||||
ArrayList<String> apiKeyList = session.get(API_KEY_LIST, ArrayList::new);
|
||||
for (String apikey : apiKeyList) {
|
||||
ApiKeyModel ak = getApiKeyModelFromCache(apikey);
|
||||
if(ak == null || ak.timeExpired()) {
|
||||
continue;
|
||||
}
|
||||
apiKeyModelList.add(ak);
|
||||
}
|
||||
return apiKeyModelList;
|
||||
}
|
||||
|
||||
|
||||
// ------------------- 请求查询
|
||||
|
||||
/**
|
||||
* 数据读取:从请求对象中读取 ApiKey,获取不到返回 null
|
||||
*/
|
||||
public String readApiKeyValue(SaRequest request) {
|
||||
|
||||
// 优先从请求参数中获取
|
||||
String apiKey = request.getParam(API_KEY_PARAMETER_NAME);
|
||||
if(SaFoxUtil.isNotEmpty(apiKey)) {
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
// 然后请求头
|
||||
apiKey = request.getHeader(API_KEY_PARAMETER_NAME);
|
||||
if(SaFoxUtil.isNotEmpty(apiKey)) {
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
// 最后从 Authorization 中获取
|
||||
apiKey = SaHttpBasicUtil.getAuthorizationValue();
|
||||
if(SaFoxUtil.isNotEmpty(apiKey)) {
|
||||
if(apiKey.endsWith(":")) {
|
||||
apiKey = apiKey.substring(0, apiKey.length() - 1);
|
||||
}
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据读取:从请求对象中读取 ApiKey,并查询到 ApiKeyModel 信息
|
||||
*/
|
||||
public ApiKeyModel currentApiKey() {
|
||||
String readApiKeyValue = readApiKeyValue(SaHolder.getRequest());
|
||||
return checkApiKey(readApiKeyValue);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ------------------- 拼接key
|
||||
|
||||
/**
|
||||
* 拼接key:ApiKey 持久化
|
||||
* @param apiKey ApiKey
|
||||
* @return key
|
||||
*/
|
||||
public String splicingApiKeySaveKey(String apiKey) {
|
||||
return getSaTokenConfig().getTokenName() + ":apikey:" + apiKey;
|
||||
}
|
||||
|
||||
|
||||
// -------- bean 对象代理
|
||||
|
||||
/**
|
||||
* 获取使用的 getSaTokenDao 实例
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public SaTokenDao getSaTokenDao() {
|
||||
return SaManager.getSaTokenDao();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取使用的 SaTokenConfig 实例
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public SaTokenConfig getSaTokenConfig() {
|
||||
return SaManager.getConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验是否开启了索引记录功能,如果未开启则抛出异常
|
||||
*/
|
||||
// protected void checkOpenRecordIndex() {
|
||||
// if(! SaManager.getSaApiKeyDataLoader().getIsRecordIndex()) {
|
||||
// SaManager.getLog().warn("当前 API Key 模块未开启索引记录功能,无法执行此操作");
|
||||
// throw new ApiKeyException("当前 API Key 模块未开启索引记录功能,无法执行此操作").setCode(SaErrorCode.CODE_12305);
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* 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.apikey;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.apikey.model.ApiKeyModel;
|
||||
import cn.dev33.satoken.context.model.SaRequest;
|
||||
import cn.dev33.satoken.session.SaSession;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* API Key 操作工具类
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class SaApiKeyUtil {
|
||||
|
||||
/**
|
||||
* 获取 ApiKeyModel,无效的 ApiKey 会返回 null
|
||||
* @param apiKey /
|
||||
* @return /
|
||||
*/
|
||||
public static ApiKeyModel getApiKey(String apiKey) {
|
||||
return SaManager.getSaApiKeyTemplate().getApiKey(apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验 ApiKey,成功返回 ApiKeyModel,失败则抛出异常
|
||||
* @param apiKey /
|
||||
* @return /
|
||||
*/
|
||||
public static ApiKeyModel checkApiKey(String apiKey) {
|
||||
return SaManager.getSaApiKeyTemplate().checkApiKey(apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 持久化:ApiKeyModel
|
||||
* @param ak /
|
||||
*/
|
||||
public static void saveApiKey(ApiKeyModel ak) {
|
||||
SaManager.getSaApiKeyTemplate().saveApiKey(ak);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 ApiKey 所代表的 LoginId
|
||||
* @param apiKey ApiKey
|
||||
* @return LoginId
|
||||
*/
|
||||
public static Object getLoginIdByApiKey(String apiKey) {
|
||||
return SaManager.getSaApiKeyTemplate().getLoginIdByApiKey(apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 ApiKey
|
||||
* @param apiKey ApiKey
|
||||
*/
|
||||
public static void deleteApiKey(String apiKey) {
|
||||
SaManager.getSaApiKeyTemplate().deleteApiKey(apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定 loginId 的所有 ApiKey
|
||||
* @param loginId /
|
||||
*/
|
||||
public static void deleteApiKeyByLoginId(Object loginId) {
|
||||
SaManager.getSaApiKeyTemplate().deleteApiKeyByLoginId(loginId);
|
||||
}
|
||||
|
||||
// ------- 创建
|
||||
|
||||
/**
|
||||
* 创建一个 ApiKeyModel 对象
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public static ApiKeyModel createApiKeyModel() {
|
||||
return SaManager.getSaApiKeyTemplate().createApiKeyModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个 ApiKeyModel 对象
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public static ApiKeyModel createApiKeyModel(Object loginId) {
|
||||
return SaManager.getSaApiKeyTemplate().createApiKeyModel(loginId);
|
||||
}
|
||||
|
||||
|
||||
// ------------------- Scope
|
||||
|
||||
/**
|
||||
* 判断:指定 ApiKey 是否具有指定 Scope 列表 (AND 模式,需要全部具备),返回 true 或 false
|
||||
* @param apiKey ApiKey
|
||||
* @param scopes 需要校验的权限列表
|
||||
*/
|
||||
public static boolean hasApiKeyScope(String apiKey, String... scopes) {
|
||||
return SaManager.getSaApiKeyTemplate().hasApiKeyScope(apiKey, scopes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验:指定 ApiKey 是否具有指定 Scope 列表 (AND 模式,需要全部具备),如果不具备则抛出异常
|
||||
* @param apiKey ApiKey
|
||||
* @param scopes 需要校验的权限列表
|
||||
*/
|
||||
public static void checkApiKeyScope(String apiKey, String... scopes) {
|
||||
SaManager.getSaApiKeyTemplate().checkApiKeyScope(apiKey, scopes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断:指定 ApiKey 是否具有指定 Scope 列表 (OR 模式,具备其一即可),返回 true 或 false
|
||||
* @param apiKey ApiKey
|
||||
* @param scopes 需要校验的权限列表
|
||||
*/
|
||||
public static boolean hasApiKeyScopeOr(String apiKey, String... scopes) {
|
||||
return SaManager.getSaApiKeyTemplate().hasApiKeyScopeOr(apiKey, scopes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验:指定 ApiKey 是否具有指定 Scope 列表 (OR 模式,具备其一即可),如果不具备则抛出异常
|
||||
* @param apiKey ApiKey
|
||||
* @param scopes 需要校验的权限列表
|
||||
*/
|
||||
public static void checkApiKeyScopeOr(String apiKey, String... scopes) {
|
||||
SaManager.getSaApiKeyTemplate().checkApiKeyScopeOr(apiKey, scopes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断:指定 ApiKey 是否属于指定 LoginId,返回 true 或 false
|
||||
* @param apiKey /
|
||||
* @param loginId /
|
||||
*/
|
||||
public static boolean isApiKeyLoginId(String apiKey, Object loginId) {
|
||||
return SaManager.getSaApiKeyTemplate().isApiKeyLoginId(apiKey, loginId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验:指定 ApiKey 是否属于指定 LoginId,如果不是则抛出异常
|
||||
*
|
||||
* @param apiKey /
|
||||
* @param loginId /
|
||||
*/
|
||||
public static void checkApiKeyLoginId(String apiKey, Object loginId) {
|
||||
SaManager.getSaApiKeyTemplate().checkApiKeyLoginId(apiKey, loginId);
|
||||
}
|
||||
|
||||
|
||||
// ------------------- 请求查询
|
||||
|
||||
/**
|
||||
* 数据读取:从请求对象中读取 ApiKey,获取不到返回 null
|
||||
*/
|
||||
public static String readApiKeyValue(SaRequest request) {
|
||||
return SaManager.getSaApiKeyTemplate().readApiKeyValue(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据读取:从请求对象中读取 ApiKey,并查询到 ApiKeyModel 信息
|
||||
*/
|
||||
public static ApiKeyModel currentApiKey() {
|
||||
return SaManager.getSaApiKeyTemplate().currentApiKey();
|
||||
}
|
||||
|
||||
|
||||
// ------------------- 索引操作
|
||||
|
||||
/**
|
||||
* 调整指定 SaSession 的 TTL 值,以保证最小化内存占用
|
||||
* @param loginId /
|
||||
* @param session 可填写 null,代表使用 loginId 现场查询
|
||||
*/
|
||||
public static void adjustIndex(Object loginId, SaSession session) {
|
||||
SaManager.getSaApiKeyTemplate().adjustIndex(loginId, session);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定 loginId 的 ApiKey 列表记录
|
||||
* @param loginId /
|
||||
* @return /
|
||||
*/
|
||||
public static List<ApiKeyModel> getApiKeyList(Object loginId) {
|
||||
return SaManager.getSaApiKeyTemplate().getApiKeyList(loginId);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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.apikey.loader;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.apikey.model.ApiKeyModel;
|
||||
|
||||
/**
|
||||
* ApiKey 数据加载器
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public interface SaApiKeyDataLoader {
|
||||
|
||||
/**
|
||||
* 获取:框架是否保存索引信息
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
default Boolean getIsRecordIndex() {
|
||||
return SaManager.getConfig().getApiKey().getIsRecordIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 apiKey 从数据库获取 ApiKeyModel 信息 (实现此方法无需为数据做缓存处理,框架内部已包含缓存逻辑)
|
||||
*
|
||||
* @param apiKey /
|
||||
* @return ApiKeyModel
|
||||
*/
|
||||
default ApiKeyModel getApiKeyModelFromDatabase(String apiKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.apikey.loader;
|
||||
|
||||
/**
|
||||
* ApiKey 数据加载器 默认实现类
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class SaApiKeyDataLoaderDefaultImpl implements SaApiKeyDataLoader {
|
||||
|
||||
// be empty of
|
||||
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
/*
|
||||
* 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.apikey.model;
|
||||
|
||||
import cn.dev33.satoken.dao.SaTokenDao;
|
||||
import cn.dev33.satoken.error.SaErrorCode;
|
||||
import cn.dev33.satoken.exception.ApiKeyException;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Model: API Key
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.41.0
|
||||
*/
|
||||
public class ApiKeyModel implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -6541180061782004705L;
|
||||
|
||||
/**
|
||||
* 名称
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 介绍
|
||||
*/
|
||||
private String intro;
|
||||
|
||||
/**
|
||||
* ApiKey 值
|
||||
*/
|
||||
private String apiKey;
|
||||
|
||||
/**
|
||||
* 账号 id
|
||||
*/
|
||||
private Object loginId;
|
||||
|
||||
/**
|
||||
* ApiKey 创建时间,13位时间戳
|
||||
*/
|
||||
private long createTime;
|
||||
|
||||
/**
|
||||
* ApiKey 到期时间,13位时间戳 (-1=永不过期)
|
||||
*/
|
||||
private long expiresTime;
|
||||
|
||||
/**
|
||||
* 是否有效 (true=生效, false=禁用)
|
||||
*/
|
||||
private Boolean isValid = true;
|
||||
|
||||
/**
|
||||
* 授权范围
|
||||
*/
|
||||
private List<String> scopes = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 扩展数据
|
||||
*/
|
||||
private Map<String, Object> extraData;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*/
|
||||
public ApiKeyModel() {
|
||||
this.createTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
|
||||
// method
|
||||
|
||||
/**
|
||||
* 添加 Scope
|
||||
* @param scope /
|
||||
* @return /
|
||||
*/
|
||||
public ApiKeyModel addScope(String ...scope) {
|
||||
if (this.scopes == null) {
|
||||
this.scopes = new ArrayList<>();
|
||||
}
|
||||
this.scopes.addAll(Arrays.asList(scope));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加 扩展数据
|
||||
* @param key /
|
||||
* @param value /
|
||||
* @return /
|
||||
*/
|
||||
public ApiKeyModel addExtra(String key, Object value) {
|
||||
if (this.extraData == null) {
|
||||
this.extraData = new LinkedHashMap<>();
|
||||
}
|
||||
this.extraData.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询扩展数据
|
||||
*/
|
||||
public Object getExtra(String key) {
|
||||
if (this.extraData == null) {
|
||||
return null;
|
||||
}
|
||||
return this.extraData.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除扩展数据
|
||||
*/
|
||||
public Object removeExtra(String key) {
|
||||
if (this.extraData == null) {
|
||||
return null;
|
||||
}
|
||||
return this.extraData.remove(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据自检,判断是否可以保存入库
|
||||
*/
|
||||
public void checkByCanSaved() {
|
||||
if (SaFoxUtil.isEmpty(this.apiKey)) {
|
||||
throw new ApiKeyException("ApiKey 值不可为空").setApiKey(apiKey).setCode(SaErrorCode.CODE_12304);
|
||||
}
|
||||
if (this.loginId == null) {
|
||||
throw new ApiKeyException("无效 ApiKey: " + apiKey).setApiKey(apiKey).setCode(SaErrorCode.CODE_12304);
|
||||
}
|
||||
if (this.createTime == 0) {
|
||||
throw new ApiKeyException("请指定 createTime 创建时间").setApiKey(apiKey).setCode(SaErrorCode.CODE_12304);
|
||||
}
|
||||
if (this.expiresTime == 0) {
|
||||
throw new ApiKeyException("请指定 expiresTime 过期时间").setApiKey(apiKey).setCode(SaErrorCode.CODE_12304);
|
||||
}
|
||||
if (this.isValid == null) {
|
||||
throw new ApiKeyException("请指定 isValid 是否生效").setApiKey(apiKey).setCode(SaErrorCode.CODE_12304);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取:此 ApiKey 的剩余有效期(秒), -1=永不过期
|
||||
* @return /
|
||||
*/
|
||||
public long expiresIn() {
|
||||
if (expiresTime == SaTokenDao.NEVER_EXPIRE) {
|
||||
return SaTokenDao.NEVER_EXPIRE;
|
||||
}
|
||||
long s = (expiresTime - System.currentTimeMillis()) / 1000;
|
||||
return s < 1 ? -2 : s;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断:此 ApiKey 是否已超时
|
||||
* @return /
|
||||
*/
|
||||
public boolean timeExpired() {
|
||||
if (expiresTime == SaTokenDao.NEVER_EXPIRE) {
|
||||
return false;
|
||||
}
|
||||
return System.currentTimeMillis() > expiresTime;
|
||||
}
|
||||
|
||||
|
||||
// get and set
|
||||
|
||||
/**
|
||||
* 获取 名称
|
||||
*
|
||||
* @return title 名称
|
||||
*/
|
||||
public String getTitle() {
|
||||
return this.title;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 名称
|
||||
*
|
||||
* @param title 名称
|
||||
* @return 对象自身
|
||||
*/
|
||||
public ApiKeyModel setTitle(String title) {
|
||||
this.title = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 介绍
|
||||
*
|
||||
* @return intro 介绍
|
||||
*/
|
||||
public String getIntro() {
|
||||
return this.intro;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 介绍
|
||||
*
|
||||
* @param intro 介绍
|
||||
* @return 对象自身
|
||||
*/
|
||||
public ApiKeyModel setIntro(String intro) {
|
||||
this.intro = intro;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 ApiKey 值
|
||||
*
|
||||
* @return apiKey ApiKey 值
|
||||
*/
|
||||
public String getApiKey() {
|
||||
return this.apiKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 ApiKey 值
|
||||
*
|
||||
* @param apiKey ApiKey 值
|
||||
* @return 对象自身
|
||||
*/
|
||||
public ApiKeyModel setApiKey(String apiKey) {
|
||||
this.apiKey = apiKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 账号 id
|
||||
*
|
||||
* @return loginId 账号 id
|
||||
*/
|
||||
public Object getLoginId() {
|
||||
return this.loginId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 账号 id
|
||||
*
|
||||
* @param loginId 账号 id
|
||||
* @return 对象自身
|
||||
*/
|
||||
public ApiKeyModel setLoginId(Object loginId) {
|
||||
this.loginId = loginId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 ApiKey 创建时间,13位时间戳
|
||||
*
|
||||
* @return createTime ApiKey 创建时间,13位时间戳
|
||||
*/
|
||||
public long getCreateTime() {
|
||||
return this.createTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 ApiKey 创建时间,13位时间戳
|
||||
*
|
||||
* @param createTime ApiKey 创建时间,13位时间戳
|
||||
* @return 对象自身
|
||||
*/
|
||||
public ApiKeyModel setCreateTime(long createTime) {
|
||||
this.createTime = createTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 ApiKey 到期时间,13位时间戳 (-1=永不过期)
|
||||
*
|
||||
* @return expiresTime ApiKey 到期时间,13位时间戳 (-1=永不过期)
|
||||
*/
|
||||
public long getExpiresTime() {
|
||||
return this.expiresTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 ApiKey 到期时间,13位时间戳 (-1=永不过期)
|
||||
*
|
||||
* @param expiresTime ApiKey 到期时间,13位时间戳 (-1=永不过期)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public ApiKeyModel setExpiresTime(long expiresTime) {
|
||||
this.expiresTime = expiresTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 是否有效 (true=生效 false=禁用)
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public Boolean getIsValid() {
|
||||
return this.isValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 是否有效 (true=生效 false=禁用)
|
||||
*
|
||||
* @param isValid /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public ApiKeyModel setIsValid(Boolean isValid) {
|
||||
this.isValid = isValid;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 授权范围
|
||||
*
|
||||
* @return scopes 授权范围
|
||||
*/
|
||||
public List<String> getScopes() {
|
||||
return this.scopes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 授权范围
|
||||
*
|
||||
* @param scopes 授权范围
|
||||
* @return 对象自身
|
||||
*/
|
||||
public ApiKeyModel setScopes(List<String> scopes) {
|
||||
this.scopes = scopes;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 扩展数据
|
||||
*
|
||||
* @return extraData 扩展数据
|
||||
*/
|
||||
public Map<String, Object> getExtraData() {
|
||||
return this.extraData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 扩展数据
|
||||
*
|
||||
* @param extraData 扩展数据
|
||||
* @return 对象自身
|
||||
*/
|
||||
public ApiKeyModel setExtraData(Map<String, Object> extraData) {
|
||||
this.extraData = extraData;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ApiKeyModel{" +
|
||||
"title='" + title +
|
||||
", intro='" + intro +
|
||||
", apiKey='" + apiKey +
|
||||
", loginId=" + loginId +
|
||||
", createTime=" + createTime +
|
||||
", expiresTime=" + expiresTime +
|
||||
", isValid=" + isValid +
|
||||
", scopes=" + scopes +
|
||||
", extraData=" + extraData +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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.config;
|
||||
|
||||
/**
|
||||
* Sa-Token API Key 相关配置
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class SaApiKeyConfig {
|
||||
|
||||
/**
|
||||
* API Key 前缀
|
||||
*/
|
||||
private String prefix = "AK-";
|
||||
|
||||
/**
|
||||
* API Key 有效期,-1=永久有效,默认30天 (修改此配置项不会影响到已创建的 API Key)
|
||||
*/
|
||||
private long timeout = 2592000;
|
||||
|
||||
/**
|
||||
* 框架是否记录索引信息
|
||||
*/
|
||||
private Boolean isRecordIndex = true;
|
||||
|
||||
/**
|
||||
* 获取 API Key 前缀
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public String getPrefix() {
|
||||
return this.prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 API Key 前缀
|
||||
*
|
||||
* @param prefix /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaApiKeyConfig setPrefix(String prefix) {
|
||||
this.prefix = prefix;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 API Key 有效期,-1=永久有效,默认30天 (修改此配置项不会影响到已创建的 API Key)
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public long getTimeout() {
|
||||
return this.timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 API Key 有效期,-1=永久有效,默认30天 (修改此配置项不会影响到已创建的 API Key)
|
||||
*
|
||||
* @param timeout /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaApiKeyConfig setTimeout(long timeout) {
|
||||
this.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 框架是否保存索引信息
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public Boolean getIsRecordIndex() {
|
||||
return this.isRecordIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 框架是否保存索引信息
|
||||
*
|
||||
* @param isRecordIndex /
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaApiKeyConfig setIsRecordIndex(Boolean isRecordIndex) {
|
||||
this.isRecordIndex = isRecordIndex;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SaApiKeyConfig{" +
|
||||
"prefix='" + prefix + '\'' +
|
||||
", timeout=" + timeout +
|
||||
", isRecordIndex=" + isRecordIndex +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -233,6 +233,10 @@ public class SaTokenConfig implements Serializable {
|
||||
*/
|
||||
public Map<String, SaSignConfig> signMany = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* API Key 相关配置
|
||||
*/
|
||||
public SaApiKeyConfig apiKey = new SaApiKeyConfig();
|
||||
|
||||
/**
|
||||
* @return token 名称 (同时也是: cookie 名称、提交 token 时参数的名称、存储 token 时的 key 前缀)
|
||||
@@ -898,6 +902,26 @@ public class SaTokenConfig implements Serializable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* API Key 相关配置
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
public SaApiKeyConfig getApiKey() {
|
||||
return this.apiKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 API Key 相关配置
|
||||
*
|
||||
* @param apiKey /
|
||||
* @return /
|
||||
*/
|
||||
public SaTokenConfig setApiKey(SaApiKeyConfig apiKey) {
|
||||
this.apiKey = apiKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
@@ -941,6 +965,7 @@ public class SaTokenConfig implements Serializable {
|
||||
+ ", cookie=" + cookie
|
||||
+ ", sign=" + sign
|
||||
+ ", signMany=" + signMany
|
||||
+ ", apiKey=" + apiKey
|
||||
+ "]";
|
||||
}
|
||||
|
||||
|
||||
@@ -204,4 +204,27 @@ public interface SaErrorCode {
|
||||
/** 未找到对应 appid 的 SaSignConfig */
|
||||
int CODE_12211 = 12211;
|
||||
|
||||
// ------------
|
||||
|
||||
/** 无效 API Key */
|
||||
int CODE_12301 = 12301;
|
||||
|
||||
/** API Key 已过期 */
|
||||
int CODE_12302 = 12302;
|
||||
|
||||
/** API Key 已被禁用 */
|
||||
int CODE_12303 = 12303;
|
||||
|
||||
/** API Key 字段自检未通过 */
|
||||
int CODE_12304 = 12304;
|
||||
|
||||
/** 未开启索引记录功能却调用了相关 API */
|
||||
int CODE_12305 = 12305;
|
||||
|
||||
/** API Key 不具有指定 Scope */
|
||||
int CODE_12311 = 12311;
|
||||
|
||||
/** API Key 不属于指定用户 */
|
||||
int CODE_12312 = 12312;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2020-2099 sa-token.cc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package cn.dev33.satoken.exception;
|
||||
|
||||
/**
|
||||
* 一个异常:代表 ApiKey 相关错误
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class ApiKeyException extends SaTokenException {
|
||||
|
||||
/**
|
||||
* 序列化版本号
|
||||
*/
|
||||
private static final long serialVersionUID = 6806129545290130114L;
|
||||
|
||||
/**
|
||||
* 一个异常:代表 ApiKey 相关错误
|
||||
* @param cause 根异常原因
|
||||
*/
|
||||
public ApiKeyException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* 一个异常:代表 ApiKey 相关错误
|
||||
* @param message 异常描述
|
||||
*/
|
||||
public ApiKeyException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 具体引起异常的 ApiKey 值
|
||||
*/
|
||||
public String apiKey;
|
||||
|
||||
public String getApiKey() {
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
public ApiKeyException setApiKey(String apiKey) {
|
||||
this.apiKey = apiKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果 flag==true,则抛出 message 异常
|
||||
* @param flag 标记
|
||||
* @param message 异常信息
|
||||
* @param code 异常细分码
|
||||
*/
|
||||
public static void throwBy(boolean flag, String message, int code) {
|
||||
if(flag) {
|
||||
throw new ApiKeyException(message).setCode(code);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2020-2099 sa-token.cc
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package cn.dev33.satoken.exception;
|
||||
|
||||
/**
|
||||
* 一个异常:代表 ApiKey Scope 相关错误
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class ApiKeyScopeException extends ApiKeyException {
|
||||
|
||||
/**
|
||||
* 序列化版本号
|
||||
*/
|
||||
private static final long serialVersionUID = 6806129545290130114L;
|
||||
|
||||
/**
|
||||
* 一个异常:代表 ApiKey Scope 相关错误
|
||||
* @param cause 根异常原因
|
||||
*/
|
||||
public ApiKeyScopeException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* 一个异常:代表 ApiKey Scope 相关错误
|
||||
* @param message 异常描述
|
||||
*/
|
||||
public ApiKeyScopeException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 具体引起异常的 ApiKey 值
|
||||
*/
|
||||
public String apiKey;
|
||||
|
||||
/**
|
||||
* 具体引起异常的 scope 值
|
||||
*/
|
||||
public String scope;
|
||||
|
||||
public String getApiKey() {
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
public ApiKeyScopeException setApiKey(String apiKey) {
|
||||
this.apiKey = apiKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getScope() {
|
||||
return scope;
|
||||
}
|
||||
|
||||
public ApiKeyScopeException setScope(String scope) {
|
||||
this.scope = scope;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果 flag==true,则抛出 message 异常
|
||||
* @param flag 标记
|
||||
* @param message 异常信息
|
||||
* @param code 异常细分码
|
||||
*/
|
||||
public static void throwBy(boolean flag, String message, int code) {
|
||||
if(flag) {
|
||||
throw new ApiKeyScopeException(message).setCode(code);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,7 +21,7 @@ import java.nio.charset.StandardCharsets;
|
||||
* Sa-Token Base32 工具类
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.41.0
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class SaBase32Util {
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ import java.time.Instant;
|
||||
* TOTP 算法类,支持 生成/验证 动态一次性密码
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.41.0
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class SaTotpTemplate {
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import cn.dev33.satoken.SaManager;
|
||||
* TOTP 工具类,支持 生成/验证 动态一次性密码
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.41.0
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class SaTotpUtil {
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.strategy.SaStrategy;
|
||||
|
||||
/**
|
||||
* SaSession 读写工具类
|
||||
*
|
||||
* @author click33
|
||||
* @since 1.42.0
|
||||
*/
|
||||
public class SaSessionRawUtil {
|
||||
|
||||
private SaSessionRawUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接Key: 在存储 SaSession 时应该使用的 key
|
||||
*
|
||||
* @param type 类型
|
||||
* @param valueId 唯一标识
|
||||
* @return sessionId
|
||||
*/
|
||||
public static String splicingSessionKey(String type, Object valueId) {
|
||||
return SaManager.getConfig().getTokenName() + ":raw-session:" + type + ":" + valueId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断:指定 SaSession 是否存在
|
||||
*
|
||||
* @param type /
|
||||
* @param valueId /
|
||||
* @return 是否存在
|
||||
*/
|
||||
public static boolean isExists(String type, Object valueId) {
|
||||
return SaManager.getSaTokenDao().getSession(splicingSessionKey(type, valueId)) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定 SaSession 对象, 如果此 SaSession 尚未在 Cache 创建,isCreate 参数代表是否则新建并返回
|
||||
*
|
||||
* @param type /
|
||||
* @param valueId /
|
||||
* @param isCreate 如果此 SaSession 尚未在 DB 创建,是否新建并返回
|
||||
* @return SaSession 对象
|
||||
*/
|
||||
public static SaSession getSessionById(String type, Object valueId, boolean isCreate) {
|
||||
String sessionId = splicingSessionKey(type, valueId);
|
||||
SaSession session = SaManager.getSaTokenDao().getSession(sessionId);
|
||||
if (session == null && isCreate) {
|
||||
session = SaStrategy.instance.createSession.apply(sessionId);
|
||||
session.setType(type);
|
||||
// TODO 过期时间
|
||||
SaManager.getSaTokenDao().setSession(session, SaManager.getConfig().getTimeout());
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定 SaSession, 如果此 SaSession 尚未在 DB 创建,则新建并返回
|
||||
*
|
||||
* @param type /
|
||||
* @param valueId /
|
||||
* @return SaSession 对象
|
||||
*/
|
||||
public static SaSession getSessionById(String type, Object valueId) {
|
||||
return getSessionById(type, valueId, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定 SaSession
|
||||
*
|
||||
* @param type /
|
||||
* @param valueId /
|
||||
*/
|
||||
public static void deleteSessionById(String type, Object valueId) {
|
||||
SaManager.getSaTokenDao().deleteSession(splicingSessionKey(type, valueId));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -66,6 +66,7 @@ public final class SaAnnotationStrategy {
|
||||
annotationHandlerMap.put(SaCheckHttpDigest.class, new SaCheckHttpDigestHandler());
|
||||
annotationHandlerMap.put(SaCheckOr.class, new SaCheckOrHandler());
|
||||
annotationHandlerMap.put(SaCheckSign.class, new SaCheckSignHandler());
|
||||
annotationHandlerMap.put(SaCheckApiKey.class, new SaCheckApiKeyHandler());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user