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); ruoyi-admin/src/main/resources/application.yml
@@ -171,8 +171,11 @@ enabled: true # AES å å¯å¤´æ è¯ headerFlag: encrypt-key # å ¬ç§é¥ éå¯¹ç§°ç®æ³çå ¬ç§é¥ å¦ï¼SM2ï¼RSA 使ç¨è 请èªè¡æ´æ¢ publicKey: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ== # ååºå å¯å ¬é¥ éå¯¹ç§°ç®æ³çå ¬ç§é¥ å¦ï¼SM2ï¼RSA 使ç¨è 请èªè¡æ´æ¢ # 对åºå端解å¯ç§é¥ MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE= publicKey: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJnNwrj4hi/y3CCJu868ghCG5dUj8wZK++RNlTLcXoMmdZWEQ/u02RgD5LyLAXGjLOjbMtC+/J9qofpSGTKSx/MCAwEAAQ== # 请æ±è§£å¯ç§é¥ éå¯¹ç§°ç®æ³çå ¬ç§é¥ å¦ï¼SM2ï¼RSA 使ç¨è 请èªè¡æ´æ¢ # 对åºå端å å¯å ¬é¥ MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ== privateKey: MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKNPuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gAkM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWowcSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99EcvDQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthhYhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3UP8iWi1Qw0Y= springdoc: 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> 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; } 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()); // 请æ±è§£å¯ requestWrapper = new DecryptRequestBodyWrapper(servletRequest, properties.getPrivateKey(), properties.getHeaderFlag()); // è·åå 坿³¨è§£ ApiEncrypt apiEncrypt = this.getApiEncryptAnnotation(servletRequest); if (ObjectUtil.isNotNull(apiEncrypt)) { // ååºå 坿 å¿ encryptFlag = apiEncrypt.response(); } else { // æ¯å¦ææ³¨è§£ï¼æå°±æ¥éï¼æ²¡ææ¾è¡ 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)) { // ä»handlerè·å注解 if (handler instanceof HandlerMethod handlerMethod) { return handlerMethod.getMethodAnnotation(ApiEncrypt.class); } } } } catch (Exception e) { throw new RuntimeException(e); } return null; } @Override public void destroy() { } } 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); 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); } }; } } 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; /** * ç§é¥ * 请æ±è§£å¯ç§é¥ */ private String privateKey; }