ruoyi-admin/pom.xml
@@ -87,6 +87,12 @@ <artifactId>JustAuth</artifactId> </dependency> <!-- æ¥å£è¯·æ±åæ°å 坿¨¡å --> <dependency> <groupId>org.dromara</groupId> <artifactId>ruoyi-common-cryptapi</artifactId> </dependency> <!-- skywalking æ´å logback --> <!-- <dependency>--> ruoyi-admin/src/main/resources/application.yml
@@ -173,6 +173,16 @@ publicKey: privateKey: # apiæ¥å£å å¯ api-decrypt: # æ¯å¦å¼å¯å ¨å±æ¥å£å å¯ enable: false # AES å å¯å¤´æ è¯ headerFlag: AES # å ¬ç§é¥ éå¯¹ç§°ç®æ³çå ¬ç§é¥ å¦ï¼SM2ï¼RSA publicKey: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ== privateKey: MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKNPuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gAkM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWowcSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99EcvDQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthhYhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3UP8iWi1Qw0Y= springdoc: api-docs: # æ¯å¦å¼å¯æ¥å£ææ¡£ ruoyi-common/pom.xml
@@ -33,6 +33,7 @@ <module>ruoyi-common-encrypt</module> <module>ruoyi-common-tenant</module> <module>ruoyi-common-websocket</module> <module>ruoyi-common-cryptapi</module> </modules> <artifactId>ruoyi-common</artifactId> ruoyi-common/ruoyi-common-bom/pom.xml
@@ -171,6 +171,13 @@ <artifactId>ruoyi-common-websocket</artifactId> <version>${revision}</version> </dependency> <!-- æ¥å£è¯·æ±åæ°å 坿¨¡å --> <dependency> <groupId>org.dromara</groupId> <artifactId>ruoyi-common-cryptapi</artifactId> <version>${revision}</version> </dependency> </dependencies> </dependencyManagement> ruoyi-common/ruoyi-common-cryptapi/pom.xml
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,34 @@ <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <groupId>org.dromara</groupId> <artifactId>ruoyi-common</artifactId> <version>${revision}</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>ruoyi-common-cryptapi</artifactId> <description> ruoyi-common-cryptapi æ¥å£è¯·æ±åæ°å 坿¨¡å </description> <dependencies> <dependency> <groupId>org.dromara</groupId> <artifactId>ruoyi-common-core</artifactId> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-crypto</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> </dependency> </dependencies> </project> ruoyi-common/ruoyi-common-cryptapi/src/main/java/org/dromara/cryptapi/annotation/ApiDecrypt.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,15 @@ package org.dromara.cryptapi.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 彿 æå½åæ³¨è§£çæ¥å£ï¼æ¥å£ç©¿å为å å¯å符串ï¼è¿è¡è§£å¯å为dtoå¯¹è±¡ï¼ ä¸å½±ååç»åæ°æ ¡éªã * @author wdhcr */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface ApiDecrypt { } ruoyi-common/ruoyi-common-cryptapi/src/main/java/org/dromara/cryptapi/config/ApiDecryptConfig.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,47 @@ package org.dromara.cryptapi.config; import cn.hutool.core.collection.CollectionUtil; import jakarta.servlet.DispatcherType; import lombok.RequiredArgsConstructor; import org.dromara.cryptapi.filter.CryptoFilter; import org.dromara.cryptapi.handler.DecryptUrlHandler; import org.dromara.cryptapi.properties.ApiDecryptProperties; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import java.util.HashMap; import java.util.List; @AutoConfiguration @RequiredArgsConstructor @EnableConfigurationProperties(ApiDecryptProperties.class) public class ApiDecryptConfig { private final DecryptUrlHandler decryptUrlHandler; private final ApiDecryptProperties apiDecryptProperties; @Bean public FilterRegistrationBean<CryptoFilter> cryptoFilterRegistration() { FilterRegistrationBean<CryptoFilter> registration = new FilterRegistrationBean<>(); registration.setDispatcherTypes(DispatcherType.REQUEST); registration.setFilter(new CryptoFilter()); List<String> urls = decryptUrlHandler.getUrls(); if (CollectionUtil.isNotEmpty(urls) || apiDecryptProperties.getEnable()) { registration.setEnabled(true); registration.addUrlPatterns(urls.toArray(new String[0])); } else { registration.setEnabled(false); } registration.setName("cryptoFilter"); HashMap<String, String> param = new HashMap<>(); param.put(CryptoFilter.CRYPTO_PUBLIC_KEY, apiDecryptProperties.getPublicKey()); param.put(CryptoFilter.CRYPTO_PRIVATE_KEY, apiDecryptProperties.getPrivateKey()); param.put(CryptoFilter.CRYPTO_HEADER_FLAG, apiDecryptProperties.getHeaderFlag()); registration.setInitParameters(param); registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE); return registration; } } ruoyi-common/ruoyi-common-cryptapi/src/main/java/org/dromara/cryptapi/core/AesEncryptor.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,57 @@ package org.dromara.cryptapi.core; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.symmetric.AES; import org.dromara.cryptapi.enums.EncodeType; import java.nio.charset.StandardCharsets; /** * AESç®æ³å®ç° * * @author è马 * @version 4.6.0 */ public class AesEncryptor { private final AES aes; public AesEncryptor(EncryptContext context) { String password = context.getPassword(); if (StrUtil.isBlank(password)) { throw new IllegalArgumentException("AES没æè·å¾ç§é¥ä¿¡æ¯"); } // aesç®æ³çç§é¥è¦æ±æ¯16ä½ã24ä½ã32ä½ int[] array = {16, 24, 32}; if (!ArrayUtil.contains(array, password.length())) { throw new IllegalArgumentException("AESç§é¥é¿åº¦åºè¯¥ä¸º16ä½ã24ä½ã32ä½ï¼å®é 为" + password.length() + "ä½"); } aes = SecureUtil.aes(context.getPassword().getBytes(StandardCharsets.UTF_8)); } /** * å å¯ * * @param value å¾ å å¯å符串 * @param encodeType å å¯åçç¼ç æ ¼å¼ */ public String encrypt(String value, EncodeType encodeType) { if (encodeType == EncodeType.HEX) { return aes.encryptHex(value); } else { return aes.encryptBase64(value); } } /** * è§£å¯ * * @param value å¾ å å¯å符串 */ public String decrypt(String value) { return this.aes.decryptStr(value); } } ruoyi-common/ruoyi-common-cryptapi/src/main/java/org/dromara/cryptapi/core/EncryptContext.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,35 @@ package org.dromara.cryptapi.core; import lombok.Data; import org.dromara.cryptapi.enums.EncodeType; /** * å å¯ä¸ä¸æ ç¨äºencryptorä¼ éå¿ è¦çåæ°ã * * @author è马 * @version 4.6.0 */ @Data public class EncryptContext { /** * å®å ¨ç§é¥ */ private String password; /** * å ¬é¥ */ private String publicKey; /** * ç§é¥ */ private String privateKey; /** * ç¼ç æ¹å¼ï¼base64/hex */ private EncodeType encode; } ruoyi-common/ruoyi-common-cryptapi/src/main/java/org/dromara/cryptapi/core/RsaEncryptor.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,52 @@ package org.dromara.cryptapi.core; import cn.hutool.core.codec.Base64; import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.asymmetric.KeyType; import cn.hutool.crypto.asymmetric.RSA; import org.dromara.common.core.utils.StringUtils; import org.dromara.cryptapi.enums.EncodeType; /** * RSAç®æ³å®ç° * * @author è马 * @version 4.6.0 */ public class RsaEncryptor { private final RSA rsa; public RsaEncryptor(EncryptContext context) { String privateKey = context.getPrivateKey(); String publicKey = context.getPublicKey(); if (StringUtils.isAnyEmpty(privateKey, publicKey)) { throw new IllegalArgumentException("RSAå ¬ç§é¥åéè¦æä¾ï¼å ¬é¥å å¯ï¼ç§é¥è§£å¯ã"); } this.rsa = SecureUtil.rsa(Base64.decode(privateKey), Base64.decode(publicKey)); } /** * å å¯ * * @param value å¾ å å¯å符串 * @param encodeType å å¯åçç¼ç æ ¼å¼ */ public String encrypt(String value, EncodeType encodeType) { if (encodeType == EncodeType.HEX) { return rsa.encryptHex(value, KeyType.PublicKey); } else { return rsa.encryptBase64(value, KeyType.PublicKey); } } /** * è§£å¯ * * @param value å¾ å å¯å符串 */ public String decrypt(String value) { return this.rsa.decryptStr(value, KeyType.PrivateKey); } } ruoyi-common/ruoyi-common-cryptapi/src/main/java/org/dromara/cryptapi/enums/EncodeType.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,13 @@ package org.dromara.cryptapi.enums; public enum EncodeType { /** * base64ç¼ç */ BASE64, /** * 16è¿å¶ç¼ç */ HEX } ruoyi-common/ruoyi-common-cryptapi/src/main/java/org/dromara/cryptapi/filter/CryptoFilter.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,54 @@ package org.dromara.cryptapi.filter; import jakarta.servlet.*; import jakarta.servlet.http.HttpServletRequest; import lombok.SneakyThrows; import org.dromara.common.core.utils.StringUtils; import org.dromara.cryptapi.core.EncryptContext; import org.dromara.cryptapi.core.RsaEncryptor; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import java.util.Objects; /** * Crypto è¿æ»¤å¨ * * @author wdhcr */ public class CryptoFilter implements Filter { public static final String CRYPTO_PUBLIC_KEY = "publicKey"; public static final String CRYPTO_PRIVATE_KEY = "privateKey"; public static final String CRYPTO_HEADER_FLAG = "headerFlag"; private RsaEncryptor rsaEncryptor; private String headerFlag; @Override public void init(FilterConfig filterConfig) { EncryptContext encryptContext = new EncryptContext(); encryptContext.setPublicKey(filterConfig.getInitParameter(CryptoFilter.CRYPTO_PUBLIC_KEY)); encryptContext.setPrivateKey(filterConfig.getInitParameter(CryptoFilter.CRYPTO_PRIVATE_KEY)); headerFlag = filterConfig.getInitParameter(CryptoFilter.CRYPTO_HEADER_FLAG); rsaEncryptor = new RsaEncryptor(encryptContext); } @SneakyThrows @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { ServletRequest requestWrapper = null; HttpServletRequest httpServletRequest = (HttpServletRequest) request; if (StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE) && (HttpMethod.PUT.matches(httpServletRequest.getMethod()) || HttpMethod.POST.matches(httpServletRequest.getMethod()))) { requestWrapper = new DecryptRequestBodyWrapper(httpServletRequest, rsaEncryptor, headerFlag); } chain.doFilter(Objects.requireNonNullElse(requestWrapper, request), response); } @Override public void destroy() { } } ruoyi-common/ruoyi-common-cryptapi/src/main/java/org/dromara/cryptapi/filter/DecryptRequestBodyWrapper.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,103 @@ package org.dromara.cryptapi.filter; import cn.hutool.core.codec.Base64; import cn.hutool.core.io.IoUtil; import jakarta.servlet.ReadListener; import jakarta.servlet.ServletInputStream; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequestWrapper; import org.dromara.common.core.constant.Constants; import org.dromara.common.core.exception.base.BaseException; import org.dromara.common.core.utils.StringUtils; import org.dromara.cryptapi.core.AesEncryptor; import org.dromara.cryptapi.core.EncryptContext; import org.dromara.cryptapi.core.RsaEncryptor; import org.dromara.cryptapi.enums.EncodeType; import org.springframework.http.MediaType; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; /** * è§£å¯è¯·æ±åæ°å·¥å ·ç±» * * @author wdhcr */ public class DecryptRequestBodyWrapper extends HttpServletRequestWrapper { private final byte[] body; public DecryptRequestBodyWrapper(HttpServletRequest request, RsaEncryptor rsaEncryptor, String headerFlag) throws IOException { super(request); String requestRsa = request.getHeader(headerFlag); if (StringUtils.isEmpty(requestRsa)) { throw new BaseException("å å¯AESç卿å¯ç ä¸è½ä¸ºç©º"); } String decryptAes = new String(Base64.decode(rsaEncryptor.decrypt(requestRsa))); request.setCharacterEncoding(Constants.UTF8); byte[] readBytes = IoUtil.readBytes(request.getInputStream(), false); String requestBody = StringUtils.toEncodedString(readBytes, StandardCharsets.UTF_8); EncryptContext encryptContext = new EncryptContext(); encryptContext.setPassword(decryptAes); encryptContext.setEncode(EncodeType.BASE64); AesEncryptor aesEncryptor = new AesEncryptor(encryptContext); String decryptBody = aesEncryptor.decrypt(requestBody); body = decryptBody.getBytes(StandardCharsets.UTF_8); } @Override public BufferedReader getReader() { return new BufferedReader(new InputStreamReader(getInputStream())); } @Override public int getContentLength() { return body.length; } @Override public long getContentLengthLong() { return body.length; } @Override public String getContentType() { return MediaType.APPLICATION_JSON_VALUE; } @Override public ServletInputStream getInputStream() { final ByteArrayInputStream bais = new ByteArrayInputStream(body); return new ServletInputStream() { @Override public int read() { return bais.read(); } @Override public int available() { return body.length; } @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } }; } } ruoyi-common/ruoyi-common-cryptapi/src/main/java/org/dromara/cryptapi/handler/DecryptUrlHandler.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,55 @@ package org.dromara.cryptapi.handler; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.ReUtil; import lombok.Data; import lombok.RequiredArgsConstructor; import org.dromara.cryptapi.annotation.ApiDecrypt; import org.springframework.beans.factory.InitializingBean; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.mvc.condition.PathPatternsRequestCondition; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import java.util.*; import java.util.regex.Pattern; /** * è·åéè¦è§£å¯çUrlé ç½® * * @author wdhcr */ @Data @Component @RequiredArgsConstructor public class DecryptUrlHandler implements InitializingBean { private static final Pattern PATTERN = Pattern.compile("\\{(.*?)}"); private List<String> urls = new ArrayList<>(); private final RequestMappingHandlerMapping requestMappingHandlerMapping; @Override public void afterPropertiesSet() { Set<String> set = new HashSet<>(); Map<RequestMappingInfo, HandlerMethod> map = requestMappingHandlerMapping.getHandlerMethods(); List<RequestMappingInfo> requestMappingInfos = map.entrySet().stream().filter(item -> { HandlerMethod method = item.getValue(); ApiDecrypt decrypt = method.getMethodAnnotation(ApiDecrypt.class); // æ æè§£å¯æ³¨è§£ç并䏿¯post æè put 请æ±çhandler return decrypt != null && CollectionUtil.containsAny(item.getKey().getMethodsCondition().getMethods(), Arrays.asList(RequestMethod.PUT, RequestMethod.POST)); }).map(Map.Entry::getKey).toList(); requestMappingInfos.forEach(info -> { // è·å注解ä¸è¾¹ç path æ¿ä»£ path variable 为 * Optional.ofNullable(info.getPathPatternsCondition()) .map(PathPatternsRequestCondition::getPatterns) .orElseGet(HashSet::new) .forEach(url -> set.add(ReUtil.replaceAll(url.getPatternString(), PATTERN, "*"))); }); urls.addAll(set); } } ruoyi-common/ruoyi-common-cryptapi/src/main/java/org/dromara/cryptapi/properties/ApiDecryptProperties.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,34 @@ package org.dromara.cryptapi.properties; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; /** * apiè§£å¯å±æ§é 置类 * @author wdhcr */ @Data @ConfigurationProperties(prefix = "api-decrypt") public class ApiDecryptProperties { /** * å å¯å¼å ³ */ private Boolean enable; /** * 头鍿 è¯ */ private String headerFlag; /** * å ¬é¥ */ private String publicKey; /** * ç§é¥ */ private String privateKey; } ruoyi-common/ruoyi-common-cryptapi/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
¶Ô±ÈÐÂÎļþ @@ -0,0 +1 @@ org.dromara.cryptapi.config.ApiDecryptConfig