From 78c91d0733ed359957a18db05930977eca75b5e5 Mon Sep 17 00:00:00 2001 From: MichelleChung <1242874891@qq.com> Date: 星期一, 27 十一月 2023 10:56:59 +0800 Subject: [PATCH] !451 响应加密功能 * update 优化调整加解密判断逻辑, 避免 NPE ; * rollback 回滚错误提交, 保留加密组件开关 ; * add 新增注解 @ApiEncrypt 用于校验接口加解密 ; * add 新增 EncryptResponseBodyWrapper 加密响应参数包装类 ; --- ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java | 2 ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/DecryptRequestBodyWrapper.java | 2 ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/EncryptResponseBodyWrapper.java | 121 ++++++++++++++++++++++++ ruoyi-common/ruoyi-common-encrypt/pom.xml | 12 ++ ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/CryptoFilter.java | 75 ++++++++++++++ ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/ApiDecryptProperties.java | 6 ruoyi-admin/src/main/resources/application.yml | 7 + ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/annotation/ApiEncrypt.java | 20 ++++ 8 files changed, 235 insertions(+), 10 deletions(-) diff --git a/ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java b/ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java index 79cd574..cf391be 100644 --- a/ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java +++ b/ruoyi-admin/src/main/java/org/dromara/web/controller/AuthController.java @@ -16,6 +16,7 @@ import org.dromara.common.core.domain.model.RegisterBody; import org.dromara.common.core.domain.model.SocialLoginBody; import org.dromara.common.core.utils.*; +import org.dromara.common.encrypt.annotation.ApiEncrypt; import org.dromara.common.json.utils.JsonUtils; import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.common.social.config.properties.SocialLoginConfigProperties; @@ -73,6 +74,7 @@ * @param body 鐧诲綍淇℃伅 * @return 缁撴灉 */ + @ApiEncrypt(response = false) @PostMapping("/login") public R<LoginVo> login(@Validated @RequestBody String body) { LoginBody loginBody = JsonUtils.parseObject(body, LoginBody.class); diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index ae20371..0ed7ccb 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -171,8 +171,11 @@ enabled: true # AES 鍔犲瘑澶存爣璇� headerFlag: encrypt-key - # 鍏閽� 闈炲绉扮畻娉曠殑鍏閽� 濡傦細SM2锛孯SA 浣跨敤鑰呰鑷鏇存崲 - publicKey: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ== + # 鍝嶅簲鍔犲瘑鍏挜 闈炲绉扮畻娉曠殑鍏閽� 濡傦細SM2锛孯SA 浣跨敤鑰呰鑷鏇存崲 + # 瀵瑰簲鍓嶇瑙e瘑绉侀挜 MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE= + publicKey: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJnNwrj4hi/y3CCJu868ghCG5dUj8wZK++RNlTLcXoMmdZWEQ/u02RgD5LyLAXGjLOjbMtC+/J9qofpSGTKSx/MCAwEAAQ== + # 璇锋眰瑙e瘑绉侀挜 闈炲绉扮畻娉曠殑鍏閽� 濡傦細SM2锛孯SA 浣跨敤鑰呰鑷鏇存崲 + # 瀵瑰簲鍓嶇鍔犲瘑鍏挜 MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ== privateKey: MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKNPuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gAkM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWowcSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99EcvDQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthhYhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3UP8iWi1Qw0Y= springdoc: diff --git a/ruoyi-common/ruoyi-common-encrypt/pom.xml b/ruoyi-common/ruoyi-common-encrypt/pom.xml index 7f58063..a1b0c5b 100644 --- a/ruoyi-common/ruoyi-common-encrypt/pom.xml +++ b/ruoyi-common/ruoyi-common-encrypt/pom.xml @@ -37,6 +37,18 @@ <artifactId>hutool-crypto</artifactId> </dependency> + <!-- SpringBoot Web瀹瑰櫒 --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-web</artifactId> + <exclusions> + <exclusion> + <artifactId>spring-boot-starter-tomcat</artifactId> + <groupId>org.springframework.boot</groupId> + </exclusion> + </exclusions> + </dependency> + </dependencies> </project> diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/annotation/ApiEncrypt.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/annotation/ApiEncrypt.java new file mode 100644 index 0000000..441cad7 --- /dev/null +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/annotation/ApiEncrypt.java @@ -0,0 +1,20 @@ +package org.dromara.common.encrypt.annotation; + +import java.lang.annotation.*; + +/** + * 寮哄埗鍔犲瘑娉ㄨВ + * + * @author Michelle.Chung + */ +@Documented +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface ApiEncrypt { + + /** + * 鍝嶅簲鍔犲瘑蹇界暐锛岄粯璁ゅ姞瀵嗭紝涓� false 鏃朵笉鍔犲瘑 + */ + boolean response() default true; + +} 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 c31e025..76ce37e 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,10 +3,19 @@ import cn.hutool.core.util.ObjectUtil; import jakarta.servlet.*; import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.dromara.common.core.constant.HttpStatus; +import org.dromara.common.core.exception.ServiceException; +import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.encrypt.annotation.ApiEncrypt; import org.dromara.common.encrypt.properties.ApiDecryptProperties; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerExceptionResolver; +import org.springframework.web.servlet.HandlerExecutionChain; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import java.io.IOException; @@ -25,8 +34,14 @@ @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - ServletRequest requestWrapper = null; HttpServletRequest servletRequest = (HttpServletRequest) request; + HttpServletResponse servletResponse = (HttpServletResponse) response; + + boolean encryptFlag = false; + ServletRequest requestWrapper = null; + ServletResponse responseWrapper = null; + EncryptResponseBodyWrapper responseBodyWrapper = null; + // 鏄惁涓� json 璇锋眰 if (StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) { // 鏄惁涓� put 鎴栬�� post 璇锋眰 @@ -34,16 +49,68 @@ // 鏄惁瀛樺湪鍔犲瘑鏍囧ご String headerValue = servletRequest.getHeader(properties.getHeaderFlag()); if (StringUtils.isNotBlank(headerValue)) { - requestWrapper = new DecryptRequestBodyWrapper(servletRequest, properties.getPublicKey(), properties.getPrivateKey(), properties.getHeaderFlag()); + // 璇锋眰瑙e瘑 + requestWrapper = new DecryptRequestBodyWrapper(servletRequest, properties.getPrivateKey(), properties.getHeaderFlag()); + // 鑾峰彇鍔犲瘑娉ㄨВ + ApiEncrypt apiEncrypt = this.getApiEncryptAnnotation(servletRequest); + if (ObjectUtil.isNotNull(apiEncrypt)) { + // 鍝嶅簲鍔犲瘑鏍囧織 + encryptFlag = apiEncrypt.response(); + } else { + // 鏄惁鏈夋敞瑙o紝鏈夊氨鎶ラ敊锛屾病鏈夋斁琛� + HandlerExceptionResolver exceptionResolver = SpringUtils.getBean("handlerExceptionResolver"); + exceptionResolver.resolveException( + servletRequest, servletResponse, null, + new ServiceException("娌℃湁璁块棶鏉冮檺锛岃鑱旂郴绠$悊鍛樻巿鏉�", HttpStatus.FORBIDDEN)); + } + } + // 鍒ゆ柇鏄惁鍝嶅簲鍔犲瘑 + 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.getPublicKey(), properties.getHeaderFlag()); + // 瀵瑰姞瀵嗗悗鐨勫唴瀹瑰啓鍑� + servletResponse.getWriter().write(encryptContent); + } + } + + /** + * 鑾峰彇 ApiEncrypt 娉ㄨВ + */ + private ApiEncrypt getApiEncryptAnnotation(HttpServletRequest servletRequest) { + RequestMappingHandlerMapping handlerMapping = SpringUtils.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class); + // 鑾峰彇娉ㄨВ + try { + HandlerExecutionChain mappingHandler = handlerMapping.getHandler(servletRequest); + System.out.println(mappingHandler); + if (ObjectUtil.isNotNull(mappingHandler)) { + Object handler = mappingHandler.getHandler(); + if (ObjectUtil.isNotNull(handler)) { + // 浠巋andler鑾峰彇娉ㄨВ + if (handler instanceof HandlerMethod handlerMethod) { + return handlerMethod.getMethodAnnotation(ApiEncrypt.class); + } + } + } + } catch (Exception e) { + throw new RuntimeException(e); + } + return null; } @Override public void destroy() { - } } diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/DecryptRequestBodyWrapper.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/DecryptRequestBodyWrapper.java index fa9a310..98f4bc7 100644 --- a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/DecryptRequestBodyWrapper.java +++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/DecryptRequestBodyWrapper.java @@ -24,7 +24,7 @@ private final byte[] body; - public DecryptRequestBodyWrapper(HttpServletRequest request, String publicKey, String privateKey, String headerFlag) throws IOException { + public DecryptRequestBodyWrapper(HttpServletRequest request, String privateKey, String headerFlag) throws IOException { super(request); // 鑾峰彇 AES 瀵嗙爜 閲囩敤 RSA 鍔犲瘑 String headerRsa = request.getHeader(headerFlag); 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 0000000..f5482de --- /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 9e25b7b..6aadb3e 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 @@ -21,14 +21,14 @@ */ private String headerFlag; - /** - * 鍏挜 + * 鍝嶅簲鍔犲瘑鍏挜 */ private String publicKey; /** - * 绉侀挜 + * 璇锋眰瑙e瘑绉侀挜 */ private String privateKey; + } -- Gitblit v1.9.3