feat(REQ-2040): 添加获取签名接口,优化验签aop逻辑
This commit is contained in:
parent
dd9fd06516
commit
96f59473ac
@ -1,7 +1,9 @@
|
||||
package cn.axzo.nanopart.api;
|
||||
|
||||
import cn.axzo.framework.domain.web.result.ApiResult;
|
||||
import cn.axzo.nanopart.api.annotation.CheckSign;
|
||||
import cn.axzo.nanopart.api.constant.enums.ListTypeEnum;
|
||||
import cn.axzo.nanopart.api.request.BlackAndWhiteListExcelImportReq;
|
||||
import cn.axzo.nanopart.api.request.BlackAndWhiteListInternalSyncReq;
|
||||
import cn.axzo.nanopart.api.request.BlackAndWhiteListPlatformSyncReq;
|
||||
import cn.axzo.nanopart.api.request.BlackAndWhiteListReq;
|
||||
@ -14,11 +16,13 @@ import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RequestPart;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
package cn.axzo.nanopart.api;
|
||||
|
||||
import cn.axzo.framework.domain.web.result.ApiResult;
|
||||
import cn.axzo.nanopart.api.request.SignatureGetReq;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import java.security.KeyPair;
|
||||
@ -24,6 +28,15 @@ public interface SignatureUtilApi {
|
||||
* @param algorithm 算法,例如:RSA
|
||||
* @return 公私钥对
|
||||
*/
|
||||
@GetMapping("/api/signature/generateKeyPair")
|
||||
@GetMapping("api/signature/generateKeyPair")
|
||||
ApiResult<Map<String,String>> generateKeyPair(@RequestParam(value = "algorithm") String algorithm);
|
||||
|
||||
/**
|
||||
* 获取签名
|
||||
*
|
||||
* @param req 请求
|
||||
* @return 签名
|
||||
*/
|
||||
@PostMapping("api/signature/getSignature")
|
||||
ApiResult<String> getSignature(@RequestBody @Validated SignatureGetReq req);
|
||||
}
|
||||
|
||||
@ -3,28 +3,34 @@ package cn.axzo.nanopart.api.aspect;
|
||||
import cn.axzo.framework.domain.web.BizException;
|
||||
import cn.axzo.framework.domain.web.code.RespCode;
|
||||
import cn.axzo.nanopart.api.annotation.CheckSign;
|
||||
import cn.axzo.nanopart.api.common.util.StringUtil;
|
||||
import cn.axzo.nanopart.api.config.SignManager;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.Signature;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author chenwenjian
|
||||
@ -35,33 +41,76 @@ import java.util.Objects;
|
||||
@Aspect
|
||||
@Order(1)
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class CheckSignAspect {
|
||||
|
||||
private final SignManager signManager;
|
||||
@Resource
|
||||
private SignManager signManager;
|
||||
|
||||
@Pointcut(value = "@annotation(cn.axzo.nanopart.api.annotation.CheckSign)")
|
||||
public void checkSign() {
|
||||
@Pointcut(value = "@annotation(org.springframework.web.bind.annotation.RequestMapping)")
|
||||
public void requestMapping() {
|
||||
}
|
||||
|
||||
@Pointcut(value = "@annotation(org.springframework.web.bind.annotation.PostMapping)")
|
||||
public void postMapping() {
|
||||
log.info("post");
|
||||
}
|
||||
|
||||
@Pointcut(value = "@annotation(org.springframework.web.bind.annotation.GetMapping)")
|
||||
public void getMapping() {
|
||||
}
|
||||
|
||||
@Pointcut(value = "@annotation(org.springframework.web.bind.annotation.PutMapping)")
|
||||
public void putMapping() {
|
||||
}
|
||||
|
||||
@Pointcut(value = "@annotation(org.springframework.web.bind.annotation.DeleteMapping)")
|
||||
public void deleteMapping() {
|
||||
}
|
||||
|
||||
@Pointcut(value = "@annotation(org.springframework.web.bind.annotation.PatchMapping)")
|
||||
public void patchMapping() {
|
||||
}
|
||||
|
||||
@Pointcut("requestMapping() || postMapping() || getMapping() || putMapping() || deleteMapping()|| patchMapping()")
|
||||
public void mappingAnnotations() {
|
||||
}
|
||||
|
||||
// @Pointcut(value = "@annotation(cn.axzo.nanopart.api.annotation.CheckSign)")
|
||||
// public void checkSign() {
|
||||
// }
|
||||
|
||||
/**
|
||||
* 切入含有@CheckSign && @RestController 注解的类
|
||||
*/
|
||||
@Before("checkSign()")
|
||||
public void verifySignature(JoinPoint joinPoint) {
|
||||
Signature signature = joinPoint.getSignature();
|
||||
MethodSignature methodSignature = (MethodSignature) signature;
|
||||
Method method = methodSignature.getMethod();
|
||||
CheckSign authorizeAnnotation = method.getAnnotation(CheckSign.class);
|
||||
log.info("before......");
|
||||
verifySignature(authorizeAnnotation);
|
||||
@Before(value = "@within(checkSign) && @within(restController)", argNames = "joinPoint,checkSign,restController")
|
||||
public void classHandler(JoinPoint joinPoint, CheckSign checkSign, RestController restController) {
|
||||
handle(joinPoint, checkSign);
|
||||
}
|
||||
|
||||
/**
|
||||
* 切入含有@checkSign && @RequestMapping/@PostMapping/@GetMapping/@PutMapping/@DeleteMapping/@PatchMapping 之一注解的方法
|
||||
*/
|
||||
@Before(value = "@annotation(checkSign)")
|
||||
public void methodHandler(JoinPoint joinPoint, CheckSign checkSign) {
|
||||
handle(joinPoint, checkSign);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public void handle(JoinPoint joinPoint, CheckSign checkSign) {
|
||||
// 验签
|
||||
verifySignature(checkSign);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验签
|
||||
*
|
||||
* @param checkSign 注解,包含签名字段名等配置
|
||||
*/
|
||||
private void verifySignature(CheckSign checkSign) {
|
||||
HttpServletRequest httpRequest = ((ServletRequestAttributes) Objects
|
||||
.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
|
||||
|
||||
// 获取调用方
|
||||
// 获取调用方appKey,检查是否是受信任调用方
|
||||
String appKey = httpRequest.getHeader("appKey");
|
||||
if (StringUtils.isEmpty(appKey)) {
|
||||
throw new BizException(new RespCode("403", "验签失败"));
|
||||
@ -73,38 +122,37 @@ public class CheckSignAspect {
|
||||
throw new BizException(new RespCode("403", "验签失败"));
|
||||
}
|
||||
|
||||
// 从Header中获取需要验签的参数字段
|
||||
StringBuilder data = new StringBuilder();
|
||||
// 从Header中获取需要验签的参数字段,若字段值为空则该字段不计入签名计算
|
||||
Map<String, String> attendFields = new HashMap<>();
|
||||
// 无指定字段则所有字段参与计算签名
|
||||
if (ArrayUtil.isEmpty(checkSign.requireSignFields())) {
|
||||
Enumeration<String> headerNames = httpRequest.getHeaderNames();
|
||||
if (CollectionUtils.isEmpty(Collections.list(headerNames))) {
|
||||
throw new BizException(new RespCode("403", "验签失败"));
|
||||
}
|
||||
while (headerNames.hasMoreElements()) {
|
||||
String headerName = headerNames.nextElement();
|
||||
String value = httpRequest.getHeader(headerName);
|
||||
if (StringUtils.isBlank(value)) {
|
||||
throw new BizException(new RespCode("403", "验签失败"));
|
||||
}
|
||||
data.append(headerName).append("=").append(value).append("&");
|
||||
attendFields.put(headerName, value);
|
||||
}
|
||||
} else {
|
||||
// 有指定参与计算签名的字段,则只提取指定字段参与计算签名
|
||||
for (String field : checkSign.requireSignFields()) {
|
||||
String value = httpRequest.getHeader(field);
|
||||
if (StringUtils.isBlank(value)) {
|
||||
throw new BizException(new RespCode("403", "验签失败"));
|
||||
}
|
||||
data.append(field).append("=").append(value);
|
||||
// 判断最后一个字段,如果是最后一个字段则不拼接"&"
|
||||
if (field.equals(checkSign.requireSignFields()[checkSign.requireSignFields().length - 1])) {
|
||||
data.append("&");
|
||||
}
|
||||
attendFields.put(field, value);
|
||||
}
|
||||
}
|
||||
log.info("data:{}", data);
|
||||
// 对所有参与签名计算的字段去除空值和去重后按照字段名的ASCII码从小到大排序(字典序)
|
||||
Map<String, Object> sortedAttendFields = attendFields.entrySet()
|
||||
.stream()
|
||||
.filter(entry -> !StringUtils.isEmpty(entry.getValue()))
|
||||
.distinct()
|
||||
.sorted(Map.Entry.comparingByKey())
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (oldValue, newValue) -> oldValue, LinkedHashMap<String, Object>::new));
|
||||
|
||||
// 拼接所有签名数据为字符串
|
||||
String data = StringUtil.extracted(sortedAttendFields);
|
||||
log.info("参与签名计算data:{}", data);
|
||||
|
||||
try {
|
||||
boolean verify = signManager.verify(appKey, data.toString(), sign);
|
||||
boolean verify = signManager.verify(appKey, data, sign);
|
||||
if (!verify) {
|
||||
throw new BizException(new RespCode("403", "验签失败"));
|
||||
}
|
||||
|
||||
@ -0,0 +1,35 @@
|
||||
package cn.axzo.nanopart.api.common.util;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author chenwenjian
|
||||
* @version 1.0
|
||||
* @date 2024/1/4 10:53
|
||||
*/
|
||||
public class StringUtil {
|
||||
|
||||
/**
|
||||
* 将Map中的参数拼接成字符串
|
||||
*
|
||||
* @param params 键值对
|
||||
* @return 拼接后的字符串
|
||||
*/
|
||||
public static String extracted(Map<String, Object> params) {
|
||||
if (CollectionUtils.isEmpty(params)) {
|
||||
return StringUtils.EMPTY;
|
||||
}
|
||||
StringBuilder data = new StringBuilder();
|
||||
for (Map.Entry<String, Object> entry : params.entrySet()) {
|
||||
if (data.length() > 0) {
|
||||
data.append("&");
|
||||
}
|
||||
data.append(entry.getKey()).append("=").append(entry.getValue().toString());
|
||||
}
|
||||
return data.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@ -28,7 +28,7 @@ public class SignManager {
|
||||
* 生成的签名为十六进制字符串
|
||||
*
|
||||
* @param appKey 调用方
|
||||
* @param rawData 待签名数据
|
||||
* @param rawData 待签名数据,格式:所有需要参与签名计算的参数去重后按照字段名的ASCII码从小到大排序(字典序)后,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串(不包含签名字段)
|
||||
* @return 签名
|
||||
*/
|
||||
public String sign(String appKey, String rawData) {
|
||||
|
||||
@ -0,0 +1,35 @@
|
||||
package cn.axzo.nanopart.api.request;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 签名获取请求
|
||||
* @author chenwenjian
|
||||
* @version 1.0
|
||||
* @date 2024/1/4 10:15
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class SignatureGetReq {
|
||||
|
||||
/**
|
||||
* 应用key,例如:nanopart
|
||||
*/
|
||||
@NotBlank(message = "appKey不能为空")
|
||||
private String appKey;
|
||||
|
||||
/**
|
||||
* 请求参数,需要参与计算签名的数据,例如:{"id":123456,"name":"张三"}
|
||||
*/
|
||||
@NotEmpty(message = "params不能为空")
|
||||
private Map<String,Object> params;
|
||||
}
|
||||
@ -3,9 +3,13 @@ package cn.axzo.nanopart.server.controller;
|
||||
import cn.axzo.framework.domain.ServiceException;
|
||||
import cn.axzo.framework.domain.web.result.ApiResult;
|
||||
import cn.axzo.nanopart.api.SignatureUtilApi;
|
||||
import cn.axzo.nanopart.api.config.SignManager;
|
||||
import cn.axzo.nanopart.api.request.SignatureGetReq;
|
||||
import cn.axzo.nanopart.api.common.util.StringUtil;
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@ -20,7 +24,17 @@ import java.util.Map;
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
public class SignatureUtilController implements SignatureUtilApi {
|
||||
|
||||
private final SignManager signManager;
|
||||
|
||||
/**
|
||||
* 生成密钥对
|
||||
*
|
||||
* @param algorithm 算法
|
||||
* @return 密钥对
|
||||
*/
|
||||
@Override
|
||||
public ApiResult<Map<String, String>> generateKeyPair(String algorithm) {
|
||||
log.info("algorithm = {}", algorithm);
|
||||
@ -35,4 +49,18 @@ public class SignatureUtilController implements SignatureUtilApi {
|
||||
log.info("keyPair = {}", JSONUtil.toJsonStr(keyPair));
|
||||
return ApiResult.ok(keyPair);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取签名
|
||||
*
|
||||
* @param req 请求
|
||||
* @return 签名
|
||||
*/
|
||||
@Override
|
||||
public ApiResult<String> getSignature(SignatureGetReq req) {
|
||||
String data = StringUtil.extracted(req.getParams());
|
||||
return ApiResult.ok(signManager.sign(req.getAppKey(), data));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user