diff --git a/src/main/java/cn/stock/market/utils/AESUtil.java b/src/main/java/cn/stock/market/utils/AESUtil.java new file mode 100644 index 0000000..70178e7 --- /dev/null +++ b/src/main/java/cn/stock/market/utils/AESUtil.java @@ -0,0 +1,119 @@ +package cn.stock.market.utils; + +import cn.hutool.core.codec.Base64; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +/** + * AES工具类 + * @author lxiaol + * @date 2021年12月23日 13:58 + */ +public class AESUtil { + /** + * 128位的AESkey + */ + private static final byte[] AES_KEY = PropertiesUtil.getProperty("aes.key","Jy112211Kj112211").getBytes(StandardCharsets.UTF_8); + + /** + * AES解密 + * + * @param data 待解密内容 + * @return 字节数组 + */ + public static byte[] decrypt(byte[] data) throws InvalidKeyException, NoSuchAlgorithmException, + NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException { + Cipher cipher = getCipher(AES_KEY, Cipher.DECRYPT_MODE); + return cipher.doFinal(data); + } + + /** + * AES 加密操作 + * + * @param data 待加密内容 + * @return 字节数组 + */ + public static byte[] encrypt(byte[] data) throws InvalidKeyException, NoSuchAlgorithmException, + NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException { + Cipher cipher = getCipher(AES_KEY, Cipher.ENCRYPT_MODE); + return cipher.doFinal(data); + } + + + /** + * AES 加密操作 + * + * @param text 待加密内容 + * @return Base64转码后的加密数据 + */ + public static String encrypt(String text) { + byte[] byteContent = text.getBytes(StandardCharsets.UTF_8); + try { + byte[] result = encrypt(byteContent);// 加密 + return Base64.encode(result);//通过Base64转码返回 + } catch (Exception e) { +// log.info("Error message: {}", e.getMessage()); + e.printStackTrace(); + } + return null; + } + + /** + * AES 解密操作 + * + * @param text + * @return + */ + public static String decrypt(String text) { + byte[] bytes = Base64.decode(text); + try { + byte[] result = decrypt(bytes); + return new String(result, StandardCharsets.UTF_8); + } catch (Exception e) { +// log.info("Error message: {}", e.getMessage()); + e.printStackTrace(); + return text; + } + } + + + /** + * 获取加密器 + * @param key + * @param model + * @return + * @throws NoSuchAlgorithmException + * @throws NoSuchPaddingException + * @throws InvalidKeyException + */ + private static Cipher getCipher(byte[] key, int model) + throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException { + SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES"); + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + cipher.init(model, secretKeySpec); + return cipher; + } + + public static void main(String[] args) { + String s = "{\n" + + " \"mobile\": \"17570717251\",\n" + + " \"password\": \"Dd112211\",\n" + + " \"platType\": 1,\n" + + " \"loginType\": 0,\n" + + " \"sourceType\": 1,\n" + + " \"equipmentModel\": \"IPHONE\",\n" + + " \"version\":1\n" + + "}"; + System.out.println(encrypt(s)); + System.out.println("Dd112211Ds112211".length()); + System.out.println(decrypt("Z3aCERCWi+nDUzv67h1/8PqS3CCuCgoj/YRTzkFNa0aNRvagi+wxpj9RsutL7nk2oo65ypyEYbjQXCI8fze8V4kMoVAb2rmoXqO3/DudVeTtY1J2784eXw+DS1QWZlZeHAHKiBaEwLcYe4XcsU9tpQIu6fE6cuSPGNetwN3C7qg6/78t4yUjCf49WW7u0/kErfgsSjMajGaVV/LOg74d2RlUcVBIIq6Us8JW3fFWPQA=")); + } + +} \ No newline at end of file diff --git a/src/main/java/cn/stock/market/utils/StreamUtil.java b/src/main/java/cn/stock/market/utils/StreamUtil.java new file mode 100644 index 0000000..a17cb63 --- /dev/null +++ b/src/main/java/cn/stock/market/utils/StreamUtil.java @@ -0,0 +1,53 @@ +package cn.stock.market.utils; + +import lombok.extern.slf4j.Slf4j; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Objects; + +/** + * 流工具类 + * @author zhouyl + * @date 2023年12月09日 15:11 + */ +@Slf4j +public class StreamUtil { + private static final Integer BUFFER_SIZE = 128; + + /** + * 将requestBody的数据转成字符串 + * + * @param inputStream + * @return + */ + public static String getBodyString(InputStream inputStream) { + StringBuilder stringBuilder = new StringBuilder(); + BufferedReader bufferedReader = null; + try { + if (Objects.nonNull(inputStream)) { + bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + int bytesRead; + char[] charBuffer = new char[BUFFER_SIZE]; + while ((bytesRead = bufferedReader.read(charBuffer)) != -1) { + stringBuilder.append(charBuffer, 0, bytesRead); + } + } + } catch (IOException e) { + log.error("get body fail,{}", e.getMessage()); + throw new RuntimeException(e); + } finally { + if (bufferedReader != null) { + try { + bufferedReader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + + } + } + return stringBuilder.toString(); + } +} diff --git a/src/main/java/cn/stock/market/web/StockApiController.java b/src/main/java/cn/stock/market/web/StockApiController.java index 101dfdc..cc78a7f 100644 --- a/src/main/java/cn/stock/market/web/StockApiController.java +++ b/src/main/java/cn/stock/market/web/StockApiController.java @@ -10,6 +10,7 @@ import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; +import cn.stock.market.web.annotations.EncryptFilter; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; @@ -137,6 +138,7 @@ public class StockApiController { @RequestMapping({"getINDStockList.do"}) @ApiOperation(value = "印度股票列表", httpMethod = "GET") @ResponseBody + @EncryptFilter(decryptRequest = false) public ServerResponse getINDStockList(@RequestParam("pageSize") Integer pageSize, @RequestParam("pageNum") Integer pageNum) throws IOException { return ServerResponse.createBySuccess(InvestingInvokerApis.of().__page(pageNum, pageSize)); } diff --git a/src/main/java/cn/stock/market/web/annotations/EncryptFilter.java b/src/main/java/cn/stock/market/web/annotations/EncryptFilter.java new file mode 100644 index 0000000..055b29e --- /dev/null +++ b/src/main/java/cn/stock/market/web/annotations/EncryptFilter.java @@ -0,0 +1,21 @@ +package cn.stock.market.web.annotations; + +import java.lang.annotation.*; + +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface EncryptFilter { + + /** + * 对入参是否解密 + * + * @return + */ + boolean decryptRequest() default true; + + /** + * 对出参是否加密 + */ + boolean encryptResponse() default true; +} diff --git a/src/main/java/cn/stock/market/web/handler/GlobalRequestBodyHandler.java b/src/main/java/cn/stock/market/web/handler/GlobalRequestBodyHandler.java new file mode 100644 index 0000000..3361aa9 --- /dev/null +++ b/src/main/java/cn/stock/market/web/handler/GlobalRequestBodyHandler.java @@ -0,0 +1,103 @@ +package cn.stock.market.web.handler; + +import cn.stock.market.utils.AESUtil; +import cn.stock.market.utils.StreamUtil; +import cn.stock.market.web.annotations.EncryptFilter; +import lombok.extern.slf4j.Slf4j; +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.StringUtils; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.util.Objects; + +/** + * 全局请求入参(RequestBody)处理器 + * + */ +@ControllerAdvice +@Slf4j +public class GlobalRequestBodyHandler extends RequestBodyAdviceAdapter { + + /** + * 该方法用于判断当前请求,是否要执行beforeBodyRead方法 + * + * @param methodParameter handler方法的参数对象 + * @param targetType handler方法的参数类型 + * @param converterType 将会使用到的Http消息转换器类类型 + * @return 返回true则会执行beforeBodyRead + */ + @Override + public boolean supports(MethodParameter methodParameter, Type targetType, + Class> converterType) { + Method method = methodParameter.getMethod(); + if (Objects.nonNull(method)) { + EncryptFilter encryptFilter = method.getAnnotation(EncryptFilter.class); + if (Objects.nonNull(encryptFilter)) { + return encryptFilter.decryptRequest(); + } + } + return false; + } + + /** + * 在Http消息转换器执转换,之前执行 + * + * @param inputMessage 客户端的请求数据 + * @param methodParameter handler方法的参数对象 + * @param targetType handler方法的参数类型 + * @param converterType 将会使用到的Http消息转换器类类型 + * @return 返回 一个自定义的HttpInputMessage + */ + @Override + public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter methodParameter, Type targetType, + Class> converterType) throws IOException { + + // 读取加密的请求体 + InputStream body = inputMessage.getBody(); + HttpHeaders headers = inputMessage.getHeaders(); + headers.remove("Content-Length"); + String s = StreamUtil.getBodyString(body); + log.info("解密前请求body:" + s); + if (StringUtils.isEmpty(s)){ + return inputMessage; + } + Method method = methodParameter.getMethod(); + if (Objects.isNull(method)) { + return inputMessage; + } + // 解密请求 + EncryptFilter encryptFilter = method.getAnnotation(EncryptFilter.class); + if (Objects.nonNull(encryptFilter) && encryptFilter.decryptRequest()) { + // 使用AES解密 + String bodyDec = AESUtil.decrypt(s); + log.info("解密后请求body:" + bodyDec); + if (!StringUtils.isEmpty(bodyDec)) { + // 使用解密后的数据,构造新的读取流 + InputStream inputStream = new ByteArrayInputStream(bodyDec.getBytes(StandardCharsets.UTF_8)); + return new HttpInputMessage() { + @Override + public HttpHeaders getHeaders() { + return inputMessage.getHeaders(); + } + + @Override + public InputStream getBody() { + return inputStream; + } + }; + } + } + return inputMessage; + } + +} diff --git a/src/main/java/cn/stock/market/web/handler/GlobalResponseBodyHandler.java b/src/main/java/cn/stock/market/web/handler/GlobalResponseBodyHandler.java new file mode 100644 index 0000000..e99497a --- /dev/null +++ b/src/main/java/cn/stock/market/web/handler/GlobalResponseBodyHandler.java @@ -0,0 +1,67 @@ +package cn.stock.market.web.handler; + +import cn.stock.market.utils.AESUtil; +import cn.stock.market.utils.PropertiesUtil; +import cn.stock.market.web.annotations.EncryptFilter; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +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.lang.reflect.Method; +import java.util.Objects; + +/** + * 全局响应结果(ResponseBody)处理器 + * + */ +@ControllerAdvice +@Slf4j +public class GlobalResponseBodyHandler implements ResponseBodyAdvice { + + private ObjectMapper objectMapper = new ObjectMapper(); + + public GlobalResponseBodyHandler(){ + } + + @Override + @SuppressWarnings("NullableProblems") // 避免 IDEA 警告 + public boolean supports(MethodParameter returnType, Class converterType) { + return true; + } + + @Override + @SuppressWarnings("NullableProblems") // 避免 IDEA 警告 + public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, + ServerHttpRequest request, ServerHttpResponse response) { + Method method = returnType.getMethod(); + if (Objects.isNull(body) || Objects.isNull(method)) { + return body; + } + log.info("处理请求地址:{} 的返回值", request.getURI()); + //获取请求数据 + String srcData = null; + try { + srcData = objectMapper.writeValueAsString(body); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + log.info("加密前响应body={}", srcData); + EncryptFilter encryptFilter = method.getAnnotation(EncryptFilter.class); + if (Objects.nonNull(encryptFilter) && encryptFilter.encryptResponse()) { + //加密 + String returnStr = AESUtil.encrypt(srcData); + log.info("加密后响应body:" + returnStr); + //添加 encrypt 告诉前端数据已加密 + return returnStr; + } + return body; + + } + +} diff --git a/src/main/resources/stock2guo.properties b/src/main/resources/stock2guo.properties index fa90f5a..245db43 100644 --- a/src/main/resources/stock2guo.properties +++ b/src/main/resources/stock2guo.properties @@ -113,3 +113,4 @@ website.domain.url=http://www.huijuwang888.com website.token=0DC8F78384C7AAFF3192A9C60A473FEE7F89C62888689616B98A06910E86B510 news.main.url=http://eminfo.eastmoney.com +aes.key=Jy112211Kj112211