mirror of https://gitee.com/stylefeng/roses
【8.1.8】【security】更新相应数据加密的过程
parent
d2c0b5b9b1
commit
b1672adcdb
|
@ -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", "响应数据时获取秘钥失败,无法加密");
|
||||
|
||||
/**
|
||||
* 错误编码
|
||||
|
|
|
@ -14,7 +14,7 @@ public class EncryptRemoveThreadLocalHolder implements RemoveThreadLocalApi {
|
|||
|
||||
@Override
|
||||
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.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());
|
||||
|
|
|
@ -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