diff --git a/kernel-d-security/security-sdk-request-encrypt-and-decode/src/main/java/cn/stylefeng/roses/kernel/security/request/encrypt/exception/enums/EncryptionExceptionEnum.java b/kernel-d-security/security-sdk-request-encrypt-and-decode/src/main/java/cn/stylefeng/roses/kernel/security/request/encrypt/exception/enums/EncryptionExceptionEnum.java index f780f7672..e46e585d7 100644 --- a/kernel-d-security/security-sdk-request-encrypt-and-decode/src/main/java/cn/stylefeng/roses/kernel/security/request/encrypt/exception/enums/EncryptionExceptionEnum.java +++ b/kernel-d-security/security-sdk-request-encrypt-and-decode/src/main/java/cn/stylefeng/roses/kernel/security/request/encrypt/exception/enums/EncryptionExceptionEnum.java @@ -14,20 +14,15 @@ import lombok.Getter; @Getter public enum EncryptionExceptionEnum implements AbstractExceptionEnum { - /** - * 请求的json解析异常 - */ - REQUEST_JSON_PARSE_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + EncryptionConstants.ENCRYPTION_EXCEPTION_STEP_CODE + "01", "请求的json解析异常"), - - /** - * 请求的json格式错误,未包含加密的data字段数据以及加密的key字段 - */ - REQUEST_JSON_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + EncryptionConstants.ENCRYPTION_EXCEPTION_STEP_CODE + "02", "请求的json格式错误,未包含加密的data字段数据以及加密的key字段"), - /** * 请求解密失败,原始数据格式错误,原始数据要符合EncryptionDTO类的规范 */ - RSA_DECRYPT_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + EncryptionConstants.ENCRYPTION_EXCEPTION_STEP_CODE + "03", "请求解密失败,原始数据格式错误"); + RSA_DECRYPT_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + EncryptionConstants.ENCRYPTION_EXCEPTION_STEP_CODE + "01", "请求解密失败,原始数据格式错误"), + + /** + * 响应数据时获取秘钥失败,无法加密 + */ + GET_SM4_KEY_ERROR(RuleConstants.BUSINESS_ERROR_TYPE_CODE + EncryptionConstants.ENCRYPTION_EXCEPTION_STEP_CODE + "02", "响应数据时获取秘钥失败,无法加密"); /** * 错误编码 diff --git a/kernel-d-security/security-sdk-request-encrypt-and-decode/src/main/java/cn/stylefeng/roses/kernel/security/request/encrypt/holder/EncryptRemoveThreadLocalHolder.java b/kernel-d-security/security-sdk-request-encrypt-and-decode/src/main/java/cn/stylefeng/roses/kernel/security/request/encrypt/holder/EncryptRemoveThreadLocalHolder.java index 6425157c7..02f2a5c53 100644 --- a/kernel-d-security/security-sdk-request-encrypt-and-decode/src/main/java/cn/stylefeng/roses/kernel/security/request/encrypt/holder/EncryptRemoveThreadLocalHolder.java +++ b/kernel-d-security/security-sdk-request-encrypt-and-decode/src/main/java/cn/stylefeng/roses/kernel/security/request/encrypt/holder/EncryptRemoveThreadLocalHolder.java @@ -14,7 +14,7 @@ public class EncryptRemoveThreadLocalHolder implements RemoveThreadLocalApi { @Override public void removeThreadLocalAction() { - EncryptionHolder.clearAesKey(); + TempSm4KeyHolder.clearSm4Key(); } } diff --git a/kernel-d-security/security-sdk-request-encrypt-and-decode/src/main/java/cn/stylefeng/roses/kernel/security/request/encrypt/holder/EncryptionHolder.java b/kernel-d-security/security-sdk-request-encrypt-and-decode/src/main/java/cn/stylefeng/roses/kernel/security/request/encrypt/holder/EncryptionHolder.java deleted file mode 100644 index 4e0f8558f..000000000 --- a/kernel-d-security/security-sdk-request-encrypt-and-decode/src/main/java/cn/stylefeng/roses/kernel/security/request/encrypt/holder/EncryptionHolder.java +++ /dev/null @@ -1,43 +0,0 @@ -package cn.stylefeng.roses.kernel.security.request.encrypt.holder; - -/** - * 用于存储响应加密秘钥 - * - * @author luojie - * @since 2021/3/23 12:54 - */ -public class EncryptionHolder { - - private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>(); - - /** - * 设置 - * - * @param aesKey aesKey - * @since 2020/8/24 - */ - public static void setAesKey(String aesKey) { - CONTEXT_HOLDER.set(aesKey); - } - - /** - * 获取 - * - * @author fengshuonan - * @since 2020/8/24 - */ - public static String getAesKey() { - return CONTEXT_HOLDER.get(); - } - - /** - * 清除 - * - * @author fengshuonan - * @since 2020/8/24 - */ - public static void clearAesKey() { - CONTEXT_HOLDER.remove(); - } - -} diff --git a/kernel-d-security/security-sdk-request-encrypt-and-decode/src/main/java/cn/stylefeng/roses/kernel/security/request/encrypt/holder/TempSm4KeyHolder.java b/kernel-d-security/security-sdk-request-encrypt-and-decode/src/main/java/cn/stylefeng/roses/kernel/security/request/encrypt/holder/TempSm4KeyHolder.java new file mode 100644 index 000000000..bacf67d86 --- /dev/null +++ b/kernel-d-security/security-sdk-request-encrypt-and-decode/src/main/java/cn/stylefeng/roses/kernel/security/request/encrypt/holder/TempSm4KeyHolder.java @@ -0,0 +1,43 @@ +package cn.stylefeng.roses.kernel.security.request.encrypt.holder; + +/** + * 存储加解密的临时秘钥,秘钥用来SM4加解密 + * + * @author fengshuonan + * @since 2024/6/28 15:13 + */ +public class TempSm4KeyHolder { + + private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>(); + + /** + * 设置SM4秘钥 + * + * @author fengshuonan + * @since 2024/6/28 15:13 + */ + public static void setSm4Key(String sm4Key) { + CONTEXT_HOLDER.set(sm4Key); + } + + /** + * 获取SM4秘钥 + * + * @author fengshuonan + * @since 2024/6/28 15:13 + */ + public static String getSm4Key() { + return CONTEXT_HOLDER.get(); + } + + /** + * 清除SM4秘钥 + * + * @author fengshuonan + * @since 2020/8/24 + */ + public static void clearSm4Key() { + CONTEXT_HOLDER.remove(); + } + +} diff --git a/kernel-d-security/security-sdk-request-encrypt-and-decode/src/main/java/cn/stylefeng/roses/kernel/security/request/encrypt/request/CustomDecryptHttpInputMessage.java b/kernel-d-security/security-sdk-request-encrypt-and-decode/src/main/java/cn/stylefeng/roses/kernel/security/request/encrypt/request/CustomDecryptHttpInputMessage.java index 3b0093c27..8d5066908 100644 --- a/kernel-d-security/security-sdk-request-encrypt-and-decode/src/main/java/cn/stylefeng/roses/kernel/security/request/encrypt/request/CustomDecryptHttpInputMessage.java +++ b/kernel-d-security/security-sdk-request-encrypt-and-decode/src/main/java/cn/stylefeng/roses/kernel/security/request/encrypt/request/CustomDecryptHttpInputMessage.java @@ -8,6 +8,7 @@ import cn.hutool.crypto.symmetric.SM4; import cn.stylefeng.roses.kernel.rule.exception.base.ServiceException; import cn.stylefeng.roses.kernel.security.guomi.GuomiUtil; import cn.stylefeng.roses.kernel.security.request.encrypt.exception.enums.EncryptionExceptionEnum; +import cn.stylefeng.roses.kernel.security.request.encrypt.holder.TempSm4KeyHolder; import cn.stylefeng.roses.kernel.security.request.encrypt.pojo.EncryptionDTO; import com.alibaba.fastjson.JSON; import org.springframework.http.HttpHeaders; @@ -66,6 +67,9 @@ public class CustomDecryptHttpInputMessage implements HttpInputMessage { String passedKey = encryptionDTO.getPassedKey(); String sm4Key = GuomiUtil.sm2DecryptWithPrivate(passedKey); + // 临时缓存Sm4Key,响应时候还需要加密 + TempSm4KeyHolder.setSm4Key(sm4Key); + // 3. 解密passedData String passedData = encryptionDTO.getPassedData(); SM4 sm4 = SmUtil.sm4(sm4Key.getBytes()); diff --git a/kernel-d-security/security-sdk-request-encrypt-and-decode/src/main/java/cn/stylefeng/roses/kernel/security/request/encrypt/response/EncryptResponseBodyAdvice.java b/kernel-d-security/security-sdk-request-encrypt-and-decode/src/main/java/cn/stylefeng/roses/kernel/security/request/encrypt/response/EncryptResponseBodyAdvice.java new file mode 100644 index 000000000..78f0e04e9 --- /dev/null +++ b/kernel-d-security/security-sdk-request-encrypt-and-decode/src/main/java/cn/stylefeng/roses/kernel/security/request/encrypt/response/EncryptResponseBodyAdvice.java @@ -0,0 +1,97 @@ +package cn.stylefeng.roses.kernel.security.request.encrypt.response; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SmUtil; +import cn.hutool.crypto.symmetric.SM4; +import cn.stylefeng.roses.kernel.rule.pojo.response.ResponseData; +import cn.stylefeng.roses.kernel.scanner.api.annotation.PostResource; +import cn.stylefeng.roses.kernel.security.request.encrypt.exception.EncryptionException; +import cn.stylefeng.roses.kernel.security.request.encrypt.exception.enums.EncryptionExceptionEnum; +import cn.stylefeng.roses.kernel.security.request.encrypt.holder.TempSm4KeyHolder; +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.MethodParameter; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.lang.Nullable; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; + +import java.nio.charset.StandardCharsets; + +/** + * 相应数据加密 + * + * @author fengshuonan + * @since 2024/6/28 14:51 + */ +@Slf4j +@ControllerAdvice +public class EncryptResponseBodyAdvice implements ResponseBodyAdvice<Object> { + + /** + * 判断必须是PostResource注解并且requiredEncryption为true + * + * @author fengshuonan + * @since 2024/6/28 14:54 + */ + @Override + public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> converterType) { + PostResource postResource = methodParameter.getAnnotatedElement().getAnnotation(PostResource.class); + if (postResource == null) { + return false; + } + return postResource.requiredEncryption(); + } + + @Override + @SuppressWarnings("rawtypes,unchecked") + public Object beforeBodyWrite(@Nullable Object originBody, + MethodParameter methodParameter, + MediaType selectedContentType, + Class selectedConverterType, + ServerHttpRequest request, + ServerHttpResponse response) { + // 原始数据为空,直接返回 + if (originBody == null) { + return null; + } + + // 判断响应实体是否是ResponseData,只针对ResponseData进行加密 + if (!(originBody instanceof ResponseData)) { + return originBody; + } + + // 转换类型,获取ResponseData中的具体数据 + ResponseData responseData = (ResponseData) originBody; + Object data = responseData.getData(); + if (data == null) { + return originBody; + } + + // 从ThreadLocal中获取解密出的SM4对称加密的秘钥 + String sm4Key = TempSm4KeyHolder.getSm4Key(); + if (StrUtil.isBlank(sm4Key)) { + throw new EncryptionException(EncryptionExceptionEnum.GET_SM4_KEY_ERROR); + } + + // 原始的Data转化为字符串,准备加密 + String originJsonString = JSON.toJSONString(data); + + // 进行SM4加密 + SM4 sm4 = SmUtil.sm4(sm4Key.getBytes()); + String encryptBase64 = sm4.encryptBase64(originJsonString, StandardCharsets.UTF_8); + + // 将加密后的数据放入ResponseData中 + responseData.setData(encryptBase64); + + // 清除当前线程中的aes key + TempSm4KeyHolder.clearSm4Key(); + + return responseData; + + } + +} diff --git a/kernel-d-security/security-sdk-request-encrypt-and-decode/src/main/java/cn/stylefeng/roses/kernel/security/request/encrypt/response/EncryptionResponseBodyAdvice.java b/kernel-d-security/security-sdk-request-encrypt-and-decode/src/main/java/cn/stylefeng/roses/kernel/security/request/encrypt/response/EncryptionResponseBodyAdvice.java deleted file mode 100644 index c66257827..000000000 --- a/kernel-d-security/security-sdk-request-encrypt-and-decode/src/main/java/cn/stylefeng/roses/kernel/security/request/encrypt/response/EncryptionResponseBodyAdvice.java +++ /dev/null @@ -1,88 +0,0 @@ -package cn.stylefeng.roses.kernel.security.request.encrypt.response; - -import cn.hutool.core.codec.Base64; -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.HexUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.crypto.SecureUtil; -import cn.hutool.crypto.symmetric.AES; -import cn.stylefeng.roses.kernel.rule.pojo.response.ResponseData; -import cn.stylefeng.roses.kernel.scanner.api.annotation.PostResource; -import cn.stylefeng.roses.kernel.security.request.encrypt.holder.EncryptionHolder; -import com.alibaba.fastjson.JSON; -import lombok.extern.slf4j.Slf4j; -import org.springframework.core.MethodParameter; -import org.springframework.http.MediaType; -import org.springframework.http.server.ServerHttpRequest; -import org.springframework.http.server.ServerHttpResponse; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; - -import java.util.Date; - -/** - * 响应结果加密 - * - * @author luojie - * @since 2021/3/23 12:54 - */ -@Slf4j -@ControllerAdvice -@SuppressWarnings("all") -public class EncryptionResponseBodyAdvice implements ResponseBodyAdvice { - - @Override - public boolean supports(MethodParameter returnType, Class converterType) { - return true; - } - - @Override - public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { - if (null != body) { - - PostResource annotation = returnType.getMethod().getAnnotation(PostResource.class); - - if (annotation != null) { - if (annotation.requiredEncryption()) { - // 判断响应实体是否是 ResponseData - if (body instanceof ResponseData) { - // 转换类型 - ResponseData responseData = (ResponseData) body; - - Object data = responseData.getData(); - - // 将返回的数据格式化成json字符串 - String respJsonStr = JSON.toJSONString(data); - - // 从 ThreadLocal 中获取 aes key - String aesKey = EncryptionHolder.getAesKey(); - - // 偏移 - byte[] iv = HexUtil.decodeHex(SecureUtil.md5(StrUtil.format("{}{}", aesKey, DateUtil.format(new Date(), "yyyyMMdd")))); - - if (StrUtil.isNotBlank(aesKey)) { - - byte[] keyByte = Base64.decode(aesKey.getBytes(CharsetUtil.CHARSET_UTF_8)); - - // AES - AES aes = new AES("CFB", "PKCS7Padding", keyByte, iv); - String encryptBase64 = aes.encryptBase64(respJsonStr); - - responseData.setData(encryptBase64); - - } - - // 清除当前线程中的aes key - EncryptionHolder.clearAesKey(); - - return responseData; - - } - } - } - - } - return body; - } -} diff --git a/kernel-d-security/security-sdk-request-encrypt-and-decode/src/main/java/cn/stylefeng/roses/kernel/security/request/encrypt/response/package-info.java b/kernel-d-security/security-sdk-request-encrypt-and-decode/src/main/java/cn/stylefeng/roses/kernel/security/request/encrypt/response/package-info.java new file mode 100644 index 000000000..968e070cf --- /dev/null +++ b/kernel-d-security/security-sdk-request-encrypt-and-decode/src/main/java/cn/stylefeng/roses/kernel/security/request/encrypt/response/package-info.java @@ -0,0 +1,4 @@ +@NonNullApi +package cn.stylefeng.roses.kernel.security.request.encrypt.response; + +import org.springframework.lang.NonNullApi; \ No newline at end of file