李亚非 3 месяцев назад
Родитель
Сommit
ac4cf69e43

+ 70 - 0
alien-config/src/main/java/shop/alien/config/advice/DecryptRequestBodyAdvice.java

@@ -0,0 +1,70 @@
+package shop.alien.config.advice;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.MethodParameter;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpInputMessage;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.util.StreamUtils;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;
+import shop.alien.config.properties.EncryptProperties;
+import shop.alien.util.encryption.Decrypt;
+import shop.alien.util.encryption.StandardAesUtil;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Type;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 请求体解密 Advice
+ * 拦截带有 @Decrypt 注解的控制器方法,自动解密 AES 加密的请求体
+ */
+@ControllerAdvice
+public class DecryptRequestBodyAdvice extends RequestBodyAdviceAdapter {
+
+    @Autowired
+    private EncryptProperties encryptProperties;
+
+    @Override
+    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
+        // 只有开启了配置,且方法或类上有 @Decrypt 注解时才拦截
+        return encryptProperties.isEnabled() && 
+               (methodParameter.hasMethodAnnotation(Decrypt.class) || 
+                methodParameter.getContainingClass().isAnnotationPresent(Decrypt.class));
+    }
+
+    @Override
+    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
+        // 读取加密的请求体
+        byte[] bodyBytes = StreamUtils.copyToByteArray(inputMessage.getBody());
+        String encryptedData = new String(bodyBytes, StandardCharsets.UTF_8);
+        
+        // 如果是 JSON 字符串格式(带有双引号),先去掉前后的双引号
+        if (encryptedData.startsWith("\"") && encryptedData.endsWith("\"")) {
+            encryptedData = encryptedData.substring(1, encryptedData.length() - 1);
+        }
+        
+        // 执行 AES 解密
+        String decryptedData = StandardAesUtil.decrypt(encryptedData, encryptProperties.getKey(), encryptProperties.getIv());
+        
+        if (decryptedData == null) {
+            throw new RuntimeException("请求数据解密失败");
+        }
+
+        // 返回解密后的输入流供后续 Jackson/Fastjson 解析
+        return new HttpInputMessage() {
+            @Override
+            public InputStream getBody() throws IOException {
+                return new ByteArrayInputStream(decryptedData.getBytes(StandardCharsets.UTF_8));
+            }
+
+            @Override
+            public HttpHeaders getHeaders() {
+                return inputMessage.getHeaders();
+            }
+        };
+    }
+}

+ 49 - 0
alien-config/src/main/java/shop/alien/config/advice/EncryptResponseBodyAdvice.java

@@ -0,0 +1,49 @@
+package shop.alien.config.advice;
+
+import com.alibaba.fastjson.JSON;
+import org.springframework.beans.factory.annotation.Autowired;
+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.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
+import shop.alien.config.properties.EncryptProperties;
+import shop.alien.util.encryption.Encrypt;
+import shop.alien.util.encryption.StandardAesUtil;
+
+/**
+ * 响应体加密 Advice
+ * 拦截带有 @Encrypt 注解的控制器方法,将返回结果 AES 加密后返回
+ */
+@ControllerAdvice
+public class EncryptResponseBodyAdvice implements ResponseBodyAdvice<Object> {
+
+    @Autowired
+    private EncryptProperties encryptProperties;
+
+    @Override
+    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
+        // 只有开启了配置,且方法或类上有 @Encrypt 注解时才拦截
+        return encryptProperties.isEnabled() && 
+               (returnType.hasMethodAnnotation(Encrypt.class) || 
+                returnType.getContainingClass().isAnnotationPresent(Encrypt.class));
+    }
+
+    @Override
+    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
+        if (body == null) {
+            return null;
+        }
+        
+        // 1. 将返回对象序列化为 JSON 字符串
+        String data = JSON.toJSONString(body);
+        
+        // 2. 执行 AES 加密
+        String encryptedData = StandardAesUtil.encrypt(data, encryptProperties.getKey(), encryptProperties.getIv());
+        
+        // 3. 返回加密后的字符串(直接写回响应流)
+        return encryptedData;
+    }
+}

+ 31 - 0
alien-config/src/main/java/shop/alien/config/properties/EncryptProperties.java

@@ -0,0 +1,31 @@
+package shop.alien.config.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.stereotype.Component;
+
+/**
+ * 加解密配置属性
+ */
+@Data
+@Component
+@RefreshScope
+@ConfigurationProperties(prefix = "alien.encrypt")
+public class EncryptProperties {
+
+    /**
+     * 是否开启加解密
+     */
+    private boolean enabled = true;
+
+    /**
+     * AES 密钥 (16位)
+     */
+    private String key;
+
+    /**
+     * AES IV (16位)
+     */
+    private String iv;
+}

+ 13 - 0
alien-store/src/main/java/shop/alien/store/controller/StorePriceController.java

@@ -12,6 +12,8 @@ import shop.alien.entity.store.StoreInfo;
 import shop.alien.entity.store.StorePrice;
 import shop.alien.mapper.StoreInfoMapper;
 import shop.alien.store.service.StorePriceService;
+import shop.alien.util.encryption.Decrypt;
+import shop.alien.util.encryption.Encrypt;
 
 import java.util.List;
 
@@ -240,5 +242,16 @@ public class StorePriceController {
         }
         return R.fail("操作失败");
     }
+
+    @ApiOperation("加解密测试接口")
+    @ApiOperationSupport(order = 10)
+    @PostMapping("/testEncryption")
+    @Decrypt
+    @Encrypt
+    public R<StorePrice> testEncryption(@RequestBody StorePrice storePrice) {
+        log.info("加解密测试接口接收数据: {}", storePrice);
+        // 原样返回,测试响应加密
+        return R.data(storePrice, "加解密测试成功");
+    }
 }
 

+ 12 - 0
alien-util/src/main/java/shop/alien/util/encryption/Decrypt.java

@@ -0,0 +1,12 @@
+package shop.alien.util.encryption;
+
+import java.lang.annotation.*;
+
+/**
+ * 解密注解,用于控制请求体是否解密
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Decrypt {
+}

+ 12 - 0
alien-util/src/main/java/shop/alien/util/encryption/Encrypt.java

@@ -0,0 +1,12 @@
+package shop.alien.util.encryption;
+
+import java.lang.annotation.*;
+
+/**
+ * 加密注解,用于控制响应体是否加密
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Encrypt {
+}

+ 11 - 0
alien-util/src/main/java/shop/alien/util/encryption/EncryptTest.java

@@ -0,0 +1,11 @@
+package shop.alien.util.encryption;
+
+public class EncryptTest {
+    public static void main(String[] args) {
+        String data = "{\"name\": \"测试商品\", \"price\": 99.9}";
+        String key = "alien67890982316";
+        String iv = "1234560405060708";
+        String encrypted = StandardAesUtil.encrypt(data, key, iv);
+        System.out.println("ENCRYPTED_RESULT:" + encrypted);
+    }
+}

+ 52 - 0
alien-util/src/main/java/shop/alien/util/encryption/StandardAesUtil.java

@@ -0,0 +1,52 @@
+package shop.alien.util.encryption;
+
+import org.apache.commons.codec.binary.Base64;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 标准 AES 加解密工具类
+ * 模式:AES/CBC/PKCS5Padding
+ * 编码:UTF-8
+ */
+public class StandardAesUtil {
+
+    private static final Logger log = LoggerFactory.getLogger(StandardAesUtil.class);
+    private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
+
+    public static String encrypt(String data, String key, String iv) {
+        try {
+            Cipher cipher = Cipher.getInstance(ALGORITHM);
+            SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
+            IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
+            
+            cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
+            byte[] encrypted = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
+            return Base64.encodeBase64String(encrypted);
+        } catch (Exception e) {
+            log.error("AES加密失败: {}", e.getMessage());
+            return null;
+        }
+    }
+
+    public static String decrypt(String base64Data, String key, String iv) {
+        try {
+            Cipher cipher = Cipher.getInstance(ALGORITHM);
+            SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
+            IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
+            
+            cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
+            byte[] decoded = Base64.decodeBase64(base64Data);
+            byte[] original = cipher.doFinal(decoded);
+            return new String(original, StandardCharsets.UTF_8);
+        } catch (Exception e) {
+            log.error("AES解密失败: {}", e.getMessage());
+            return null;
+        }
+    }
+}