mirror of https://gitee.com/stylefeng/roses
【8.1.8】【security】更新相应数据加密的过程
parent
d2c0b5b9b1
commit
b1672adcdb
|
@ -14,20 +14,15 @@ import lombok.Getter;
|
||||||
@Getter
|
@Getter
|
||||||
public enum EncryptionExceptionEnum implements AbstractExceptionEnum {
|
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类的规范
|
* 请求解密失败,原始数据格式错误,原始数据要符合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", "响应数据时获取秘钥失败,无法加密");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 错误编码
|
* 错误编码
|
||||||
|
|
|
@ -14,7 +14,7 @@ public class EncryptRemoveThreadLocalHolder implements RemoveThreadLocalApi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeThreadLocalAction() {
|
public void removeThreadLocalAction() {
|
||||||
EncryptionHolder.clearAesKey();
|
TempSm4KeyHolder.clearSm4Key();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ import cn.hutool.crypto.symmetric.SM4;
|
||||||
import cn.stylefeng.roses.kernel.rule.exception.base.ServiceException;
|
import cn.stylefeng.roses.kernel.rule.exception.base.ServiceException;
|
||||||
import cn.stylefeng.roses.kernel.security.guomi.GuomiUtil;
|
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.exception.enums.EncryptionExceptionEnum;
|
||||||
|
import cn.stylefeng.roses.kernel.security.request.encrypt.holder.TempSm4KeyHolder;
|
||||||
import cn.stylefeng.roses.kernel.security.request.encrypt.pojo.EncryptionDTO;
|
import cn.stylefeng.roses.kernel.security.request.encrypt.pojo.EncryptionDTO;
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSON;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
|
@ -66,6 +67,9 @@ public class CustomDecryptHttpInputMessage implements HttpInputMessage {
|
||||||
String passedKey = encryptionDTO.getPassedKey();
|
String passedKey = encryptionDTO.getPassedKey();
|
||||||
String sm4Key = GuomiUtil.sm2DecryptWithPrivate(passedKey);
|
String sm4Key = GuomiUtil.sm2DecryptWithPrivate(passedKey);
|
||||||
|
|
||||||
|
// 临时缓存Sm4Key,响应时候还需要加密
|
||||||
|
TempSm4KeyHolder.setSm4Key(sm4Key);
|
||||||
|
|
||||||
// 3. 解密passedData
|
// 3. 解密passedData
|
||||||
String passedData = encryptionDTO.getPassedData();
|
String passedData = encryptionDTO.getPassedData();
|
||||||
SM4 sm4 = SmUtil.sm4(sm4Key.getBytes());
|
SM4 sm4 = SmUtil.sm4(sm4Key.getBytes());
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
@NonNullApi
|
||||||
|
package cn.stylefeng.roses.kernel.security.request.encrypt.response;
|
||||||
|
|
||||||
|
import org.springframework.lang.NonNullApi;
|
Loading…
Reference in New Issue