mirror of
https://github.com/continew-org/continew-admin.git
synced 2025-09-12 06:57:13 +08:00
refactor(open): 优化 API 参数签名处理
This commit is contained in:
@@ -19,7 +19,7 @@ package top.continew.admin.open.handler;
|
|||||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||||
import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface;
|
import cn.dev33.satoken.annotation.handler.SaAnnotationHandlerInterface;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import top.continew.admin.open.util.ApiSignCheckUtils;
|
import top.continew.admin.open.util.OpenApiUtils;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
@@ -41,9 +41,8 @@ public class SaCheckPermissionHandler implements SaAnnotationHandlerInterface<Sa
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void checkMethod(SaCheckPermission at, Method method) {
|
public void checkMethod(SaCheckPermission at, Method method) {
|
||||||
if (!ApiSignCheckUtils.isSignParamExists()) {
|
if (!OpenApiUtils.isSignParamExists()) {
|
||||||
_checkMethod(at.type(), at.value(), at.mode(), at.orRole());
|
_checkMethod(at.type(), at.value(), at.mode(), at.orRole());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -70,4 +70,16 @@ public class AppDO extends BaseDO {
|
|||||||
* 状态
|
* 状态
|
||||||
*/
|
*/
|
||||||
private DisEnableStatusEnum status;
|
private DisEnableStatusEnum status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否已过期
|
||||||
|
*
|
||||||
|
* @return true:已过期;false:未过期
|
||||||
|
*/
|
||||||
|
public boolean isExpired() {
|
||||||
|
if (expireTime == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return LocalDateTime.now().isAfter(expireTime);
|
||||||
|
}
|
||||||
}
|
}
|
@@ -45,6 +45,6 @@ public class AppSecretResp implements Serializable {
|
|||||||
/**
|
/**
|
||||||
* Secret Key(私有密钥)
|
* Secret Key(私有密钥)
|
||||||
*/
|
*/
|
||||||
@Schema(description = "Secret Key(私有密钥)", example = "")
|
@Schema(description = "Secret Key(私有密钥)", example = "MDI2YzQ3YTU1NGEyNDM1ZWIwNTU5NmNjNmZjM2M2Nzg=")
|
||||||
private String secretKey;
|
private String secretKey;
|
||||||
}
|
}
|
||||||
|
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package top.continew.admin.open.service;
|
package top.continew.admin.open.service;
|
||||||
|
|
||||||
|
import top.continew.admin.open.model.entity.AppDO;
|
||||||
import top.continew.admin.open.model.query.AppQuery;
|
import top.continew.admin.open.model.query.AppQuery;
|
||||||
import top.continew.admin.open.model.req.AppReq;
|
import top.continew.admin.open.model.req.AppReq;
|
||||||
import top.continew.admin.open.model.resp.AppDetailResp;
|
import top.continew.admin.open.model.resp.AppDetailResp;
|
||||||
@@ -48,26 +49,10 @@ public interface AppService extends BaseService<AppResp, AppDetailResp, AppQuery
|
|||||||
void resetSecret(Long id);
|
void resetSecret(Long id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据 Access Key 获取 Secret Key
|
* 根据 Access Key 查询
|
||||||
*
|
*
|
||||||
* @param accessKey Access Key
|
* @param accessKey Access Key
|
||||||
* @return Secret Key
|
* @return 应用信息
|
||||||
*/
|
*/
|
||||||
String getSecretKeyByAccessKey(String accessKey);
|
AppDO getByAccessKey(String accessKey);
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断应用是否存在
|
|
||||||
*
|
|
||||||
* @param accessKey Access Key
|
|
||||||
* @return 是否存在(true:存在;false:不存在)
|
|
||||||
*/
|
|
||||||
boolean isAppExists(String accessKey);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断应用密钥是否过期
|
|
||||||
*
|
|
||||||
* @param accessKey Access Key
|
|
||||||
* @return 是否过期(true:已过期;false:未过期)
|
|
||||||
*/
|
|
||||||
boolean isAppSecretExpired(String accessKey);
|
|
||||||
}
|
}
|
@@ -17,7 +17,6 @@
|
|||||||
package top.continew.admin.open.service.impl;
|
package top.continew.admin.open.service.impl;
|
||||||
|
|
||||||
import cn.hutool.core.codec.Base64;
|
import cn.hutool.core.codec.Base64;
|
||||||
import cn.hutool.core.date.DateUtil;
|
|
||||||
import cn.hutool.core.util.IdUtil;
|
import cn.hutool.core.util.IdUtil;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@@ -31,11 +30,8 @@ import top.continew.admin.open.model.resp.AppResp;
|
|||||||
import top.continew.admin.open.model.resp.AppSecretResp;
|
import top.continew.admin.open.model.resp.AppSecretResp;
|
||||||
import top.continew.admin.open.service.AppService;
|
import top.continew.admin.open.service.AppService;
|
||||||
import top.continew.starter.core.constant.StringConstants;
|
import top.continew.starter.core.constant.StringConstants;
|
||||||
import top.continew.starter.core.validation.ValidationUtils;
|
|
||||||
import top.continew.starter.extension.crud.service.impl.BaseServiceImpl;
|
import top.continew.starter.extension.crud.service.impl.BaseServiceImpl;
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 应用业务实现
|
* 应用业务实现
|
||||||
*
|
*
|
||||||
@@ -74,23 +70,8 @@ public class AppServiceImpl extends BaseServiceImpl<AppMapper, AppDO, AppResp, A
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getSecretKeyByAccessKey(String accessKey) {
|
public AppDO getByAccessKey(String accessKey) {
|
||||||
return Optional.ofNullable(baseMapper.selectByAccessKey(accessKey)).map(AppDO::getSecretKey).orElse(null);
|
return baseMapper.selectByAccessKey(accessKey);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isAppExists(String accessKey) {
|
|
||||||
return baseMapper.selectByAccessKey(accessKey) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isAppSecretExpired(String accessKey) {
|
|
||||||
AppDO app = baseMapper.selectByAccessKey(accessKey);
|
|
||||||
ValidationUtils.throwIfNull(app, "应用不存在");
|
|
||||||
if (app.getExpireTime() == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return app.getExpireTime().isBefore(DateUtil.date().toLocalDateTime());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -19,11 +19,12 @@ package top.continew.admin.open.sign;
|
|||||||
import cn.dev33.satoken.sign.SaSignTemplate;
|
import cn.dev33.satoken.sign.SaSignTemplate;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
import top.continew.admin.common.enums.DisEnableStatusEnum;
|
||||||
|
import top.continew.admin.open.model.entity.AppDO;
|
||||||
import top.continew.admin.open.service.AppService;
|
import top.continew.admin.open.service.AppService;
|
||||||
import top.continew.starter.core.validation.ValidationUtils;
|
import top.continew.starter.core.validation.ValidationUtils;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TreeMap;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API 参数签名算法
|
* API 参数签名算法
|
||||||
@@ -52,32 +53,24 @@ public class OpenApiSignTemplate extends SaSignTemplate {
|
|||||||
ValidationUtils.throwIfBlank(nonceValue, "nonce不能为空");
|
ValidationUtils.throwIfBlank(nonceValue, "nonce不能为空");
|
||||||
ValidationUtils.throwIfBlank(signValue, "sign不能为空");
|
ValidationUtils.throwIfBlank(signValue, "sign不能为空");
|
||||||
ValidationUtils.throwIfBlank(accessKeyValue, "accessKey不能为空");
|
ValidationUtils.throwIfBlank(accessKeyValue, "accessKey不能为空");
|
||||||
ValidationUtils.throwIf(!appService.isAppExists(accessKeyValue), "accessKey非法");
|
AppDO app = appService.getByAccessKey(accessKeyValue);
|
||||||
ValidationUtils.throwIf(appService.isAppSecretExpired(accessKeyValue), "密钥已过期, 请重置密钥");
|
ValidationUtils.throwIfNull(app, "accessKey非法");
|
||||||
|
ValidationUtils.throwIfEqual(DisEnableStatusEnum.DISABLE, app.getStatus(), "应用已被禁用, 请联系管理员");
|
||||||
|
ValidationUtils.throwIf(app.isExpired(), "应用已过期, 请联系管理员");
|
||||||
|
|
||||||
// 依次校验三个参数
|
// 依次校验三个参数
|
||||||
super.checkTimestamp(Long.parseLong(timestampValue));
|
super.checkTimestamp(Long.parseLong(timestampValue));
|
||||||
super.checkNonce(nonceValue);
|
super.checkNonce(nonceValue);
|
||||||
|
paramMap.put(key, app.getSecretKey());
|
||||||
super.checkSign(paramMap, signValue);
|
super.checkSign(paramMap, signValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String createSign(Map<String, ?> paramsMap) {
|
public String createSign(Map<String, ?> paramMap) {
|
||||||
// 根据 AK 获取 SK
|
ValidationUtils.throwIfEmpty(paramMap.get(key), "秘钥缺失, 请检查应用配置");
|
||||||
String accessKeyValue = (String)((Map)paramsMap).get(ACCESS_KEY);
|
// 移除 sign 参数
|
||||||
String secretKey = appService.getSecretKeyByAccessKey(accessKeyValue);
|
paramMap.remove(sign);
|
||||||
ValidationUtils.throwIfBlank(secretKey, "密钥缺失, 请检查应用配置");
|
|
||||||
|
|
||||||
// 如果调用者不小心传入了 sign 参数,则此处需要将 sign 参数排除在外
|
|
||||||
if (paramsMap.containsKey(sign)) {
|
|
||||||
// 为了保证不影响原有的 paramsMap,此处需要再复制一份
|
|
||||||
paramsMap = new TreeMap<>(paramsMap);
|
|
||||||
paramsMap.remove(sign);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算签名
|
// 计算签名
|
||||||
String paramsStr = super.joinParamsDictSort(paramsMap);
|
return super.abstractStr(super.joinParamsDictSort(paramMap));
|
||||||
String fullStr = paramsStr + "&" + key + "=" + secretKey;
|
|
||||||
return super.abstractStr(fullStr);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -23,14 +23,15 @@ import cn.dev33.satoken.sign.SaSignTemplate;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API签名验证工具类
|
* Open Api 工具类
|
||||||
*
|
*
|
||||||
* @author chengzi
|
* @author chengzi
|
||||||
|
* @author Charles7c
|
||||||
* @since 2024/10/25 15:31
|
* @since 2024/10/25 15:31
|
||||||
*/
|
*/
|
||||||
public class ApiSignCheckUtils {
|
public class OpenApiUtils {
|
||||||
|
|
||||||
private ApiSignCheckUtils() {
|
private OpenApiUtils() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
Reference in New Issue
Block a user