diff --git a/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/BlackAndWhiteListApi.java b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/BlackAndWhiteListApi.java index d12961cb..13eb4836 100644 --- a/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/BlackAndWhiteListApi.java +++ b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/BlackAndWhiteListApi.java @@ -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; /** diff --git a/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/SignatureUtilApi.java b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/SignatureUtilApi.java index 543a1763..9d63fa35 100644 --- a/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/SignatureUtilApi.java +++ b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/SignatureUtilApi.java @@ -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> generateKeyPair(@RequestParam(value = "algorithm") String algorithm); + + /** + * 获取签名 + * + * @param req 请求 + * @return 签名 + */ + @PostMapping("api/signature/getSignature") + ApiResult getSignature(@RequestBody @Validated SignatureGetReq req); } diff --git a/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/aspect/CheckSignAspect.java b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/aspect/CheckSignAspect.java index f8c518e6..704bb61d 100644 --- a/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/aspect/CheckSignAspect.java +++ b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/aspect/CheckSignAspect.java @@ -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 attendFields = new HashMap<>(); + // 无指定字段则所有字段参与计算签名 if (ArrayUtil.isEmpty(checkSign.requireSignFields())) { Enumeration 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 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::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", "验签失败")); } diff --git a/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/common/util/StringUtil.java b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/common/util/StringUtil.java new file mode 100644 index 00000000..0fb23dd6 --- /dev/null +++ b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/common/util/StringUtil.java @@ -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 params) { + if (CollectionUtils.isEmpty(params)) { + return StringUtils.EMPTY; + } + StringBuilder data = new StringBuilder(); + for (Map.Entry entry : params.entrySet()) { + if (data.length() > 0) { + data.append("&"); + } + data.append(entry.getKey()).append("=").append(entry.getValue().toString()); + } + return data.toString(); + } + +} diff --git a/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/config/SignManager.java b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/config/SignManager.java index d329bad2..d3121de9 100644 --- a/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/config/SignManager.java +++ b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/config/SignManager.java @@ -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) { diff --git a/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/request/SignatureGetReq.java b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/request/SignatureGetReq.java new file mode 100644 index 00000000..30ef7cc2 --- /dev/null +++ b/black-list/black-list-api/src/main/java/cn/axzo/nanopart/api/request/SignatureGetReq.java @@ -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 params; +} diff --git a/black-list/black-list-service/src/main/java/cn/axzo/nanopart/server/controller/SignatureUtilController.java b/black-list/black-list-service/src/main/java/cn/axzo/nanopart/server/controller/SignatureUtilController.java index 2a8226ff..d4ee91e6 100644 --- a/black-list/black-list-service/src/main/java/cn/axzo/nanopart/server/controller/SignatureUtilController.java +++ b/black-list/black-list-service/src/main/java/cn/axzo/nanopart/server/controller/SignatureUtilController.java @@ -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> 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 getSignature(SignatureGetReq req) { + String data = StringUtil.extracted(req.getParams()); + return ApiResult.ok(signManager.sign(req.getAppKey(), data)); + } + + }