From 00710e0d24317e7f5b48d9b485111379045cb2d2 Mon Sep 17 00:00:00 2001 From: "Michelle.Chung" <1242874891@qq.com> Date: Mon, 20 Nov 2023 19:19:48 +0800 Subject: [PATCH] =?UTF-8?q?add=20=E6=96=B0=E5=A2=9E=20EncryptResponseBodyW?= =?UTF-8?q?rapper=20=E5=8A=A0=E5=AF=86=E5=93=8D=E5=BA=94=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E5=8C=85=E8=A3=85=E7=B1=BB=20;=20update=20=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=20application,=20ApiDecryptProperties=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=93=8D=E5=BA=94=E5=8A=A0=E5=AF=86=E5=8F=82=E6=95=B0=20;=20up?= =?UTF-8?q?date=20=E6=9B=B4=E6=96=B0=20CryptoFilter=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=93=8D=E5=BA=94=E5=8A=A0=E5=AF=86=E9=80=BB=E8=BE=91=20;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application.yml | 7 +- ruoyi-common/ruoyi-common-encrypt/pom.xml | 12 ++ .../common/encrypt/filter/CryptoFilter.java | 24 +++- .../filter/EncryptResponseBodyWrapper.java | 121 ++++++++++++++++++ .../properties/ApiDecryptProperties.java | 21 ++- 5 files changed, 179 insertions(+), 6 deletions(-) create mode 100644 ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/EncryptResponseBodyWrapper.java diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index ae20371ca..5e4f07190 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -169,11 +169,16 @@ mybatis-encryptor: api-decrypt: # 是否开启全局接口加密 enabled: true + # 是否开启响应加密 注:响应加密会有性能影响,默认关闭 + responseEnabled: false # AES 加密头标识 headerFlag: encrypt-key - # 公私钥 非对称算法的公私钥 如:SM2,RSA 使用者请自行更换 + # 请求加密公私钥 非对称算法的公私钥 如:SM2,RSA 使用者请自行更换 publicKey: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ== privateKey: MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKNPuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gAkM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWowcSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99EcvDQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthhYhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3UP8iWi1Qw0Y= + # 响应加密公私钥 非对称算法的公私钥 如:SM2,RSA 使用者请自行更换 + responsePublicKey: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJnNwrj4hi/y3CCJu868ghCG5dUj8wZK++RNlTLcXoMmdZWEQ/u02RgD5LyLAXGjLOjbMtC+/J9qofpSGTKSx/MCAwEAAQ== + responsePrivateKey: MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE= springdoc: api-docs: diff --git a/ruoyi-common/ruoyi-common-encrypt/pom.xml b/ruoyi-common/ruoyi-common-encrypt/pom.xml index 7f580637d..a1b0c5bb8 100644 --- a/ruoyi-common/ruoyi-common-encrypt/pom.xml +++ b/ruoyi-common/ruoyi-common-encrypt/pom.xml @@ -37,6 +37,18 @@ hutool-crypto + + + org.springframework.boot + spring-boot-starter-web + + + spring-boot-starter-tomcat + org.springframework.boot + + + + diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/CryptoFilter.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/CryptoFilter.java index c31e025ac..af7ee0498 100644 --- a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/CryptoFilter.java +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/CryptoFilter.java @@ -3,6 +3,7 @@ package org.dromara.common.encrypt.filter; import cn.hutool.core.util.ObjectUtil; import jakarta.servlet.*; import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import org.dromara.common.core.utils.StringUtils; import org.dromara.common.encrypt.properties.ApiDecryptProperties; import org.springframework.http.HttpMethod; @@ -25,8 +26,12 @@ public class CryptoFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + boolean encryptFlag = properties.getResponseEnabled(); ServletRequest requestWrapper = null; + ServletResponse responseWrapper = null; + EncryptResponseBodyWrapper responseBodyWrapper = null; HttpServletRequest servletRequest = (HttpServletRequest) request; + HttpServletResponse servletResponse = (HttpServletResponse) response; // 是否为 json 请求 if (StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) { // 是否为 put 或者 post 请求 @@ -36,14 +41,29 @@ public class CryptoFilter implements Filter { if (StringUtils.isNotBlank(headerValue)) { requestWrapper = new DecryptRequestBodyWrapper(servletRequest, properties.getPublicKey(), properties.getPrivateKey(), properties.getHeaderFlag()); } + // 判断是否响应加密 + if (encryptFlag) { + responseBodyWrapper = new EncryptResponseBodyWrapper(servletResponse); + responseWrapper = responseBodyWrapper; + } } } - chain.doFilter(ObjectUtil.defaultIfNull(requestWrapper, request), response); + chain.doFilter( + ObjectUtil.defaultIfNull(requestWrapper, request), + ObjectUtil.defaultIfNull(responseWrapper, response)); + + if (encryptFlag) { + servletResponse.reset(); + // 对原始内容加密 + String encryptContent = responseBodyWrapper.getEncryptContent( + servletResponse, properties.getResponsePublicKey(), properties.getHeaderFlag()); + // 对加密后的内容写出 + servletResponse.getWriter().write(encryptContent); + } } @Override public void destroy() { - } } diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/EncryptResponseBodyWrapper.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/EncryptResponseBodyWrapper.java new file mode 100644 index 000000000..f5482de24 --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/EncryptResponseBodyWrapper.java @@ -0,0 +1,121 @@ +package org.dromara.common.encrypt.filter; + +import cn.hutool.core.util.RandomUtil; +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.WriteListener; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletResponseWrapper; +import org.dromara.common.encrypt.utils.EncryptUtils; + +import java.io.*; +import java.nio.charset.StandardCharsets; + +/** + * 加密响应参数包装类 + * + * @author Michelle.Chung + */ +public class EncryptResponseBodyWrapper extends HttpServletResponseWrapper { + + private final ByteArrayOutputStream byteArrayOutputStream; + private final ServletOutputStream servletOutputStream; + private final PrintWriter printWriter; + + public EncryptResponseBodyWrapper(HttpServletResponse response) throws IOException { + super(response); + this.byteArrayOutputStream = new ByteArrayOutputStream(); + this.servletOutputStream = this.getOutputStream(); + this.printWriter = new PrintWriter(new OutputStreamWriter(byteArrayOutputStream)); + } + + @Override + public PrintWriter getWriter() { + return printWriter; + } + + @Override + public void flushBuffer() throws IOException { + if (servletOutputStream != null) { + servletOutputStream.flush(); + } + if (printWriter != null) { + printWriter.flush(); + } + } + + @Override + public void reset() { + byteArrayOutputStream.reset(); + } + + public byte[] getResponseData() throws IOException { + flushBuffer(); + return byteArrayOutputStream.toByteArray(); + } + + public String getContent() throws IOException { + flushBuffer(); + return byteArrayOutputStream.toString(); + } + + /** + * 获取加密内容 + * + * @param servletResponse response + * @param publicKey RSA公钥 (用于加密 AES 秘钥) + * @param headerFlag 请求头标志 + * @return 加密内容 + * @throws IOException + */ + public String getEncryptContent(HttpServletResponse servletResponse, String publicKey, String headerFlag) throws IOException { + // 生成秘钥 + String aesPassword = RandomUtil.randomString(32); + System.out.println("aesPassword = " + aesPassword); + // 秘钥使用 Base64 编码 + String encryptAes = EncryptUtils.encryptByBase64(aesPassword); + // Rsa 公钥加密 Base64 编码 + String encryptPassword = EncryptUtils.encryptByRsa(encryptAes, publicKey); + + // 设置响应头 + servletResponse.setHeader(headerFlag, encryptPassword); + servletResponse.setHeader("Access-Control-Allow-Origin", "*"); + servletResponse.setHeader("Access-Control-Allow-Methods", "*"); + servletResponse.setCharacterEncoding(StandardCharsets.UTF_8.toString()); + + // 获取原始内容 + String originalBody = this.getContent(); + // 对内容进行加密 + return EncryptUtils.encryptByAes(originalBody, aesPassword); + } + + @Override + public ServletOutputStream getOutputStream() throws IOException { + return new ServletOutputStream() { + @Override + public boolean isReady() { + return false; + } + + @Override + public void setWriteListener(WriteListener writeListener) { + + } + + @Override + public void write(int b) throws IOException { + byteArrayOutputStream.write(b); + } + + @Override + public void write(byte[] b) throws IOException { + byteArrayOutputStream.write(b); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + byteArrayOutputStream.write(b, off, len); + } + }; + } + +} diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/ApiDecryptProperties.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/ApiDecryptProperties.java index 9e25b7b9a..95e0f0a7b 100644 --- a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/ApiDecryptProperties.java +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/ApiDecryptProperties.java @@ -16,19 +16,34 @@ public class ApiDecryptProperties { */ private Boolean enabled; + /** + * 响应加密开关 + */ + private Boolean responseEnabled; + /** * 头部标识 */ private String headerFlag; - /** - * 公钥 + * 请求加密公钥 */ private String publicKey; /** - * 私钥 + * 请求加密私钥 */ private String privateKey; + + /** + * 响应加密公钥 + */ + private String responsePublicKey; + + /** + * 响应加密私钥 + */ + private String responsePrivateKey; + }