add 新增 请求加密传输 合并优化 !pr377

feature/model
疯狂的狮子Li 1 year ago
parent 10b5b0e82a
commit af08632c37

@ -87,13 +87,6 @@
<artifactId>JustAuth</artifactId>
</dependency>
<!-- 接口请求参数加密模块 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-cryptapi</artifactId>
</dependency>
<!-- skywalking 整合 logback -->
<!-- <dependency>-->
<!-- <groupId>org.apache.skywalking</groupId>-->

@ -176,10 +176,10 @@ mybatis-encryptor:
# api接口加密
api-decrypt:
# 是否开启全局接口加密
enable: false
enabled: true
# AES 加密头标识
headerFlag: AES
# 公私钥 非对称算法的公私钥 如SM2RSA
headerFlag: encrypt-key
# 公私钥 非对称算法的公私钥 如SM2RSA 使用者请自行更换
publicKey: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==
privateKey: MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKNPuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gAkM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWowcSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99EcvDQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthhYhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3UP8iWi1Qw0Y=

@ -33,7 +33,6 @@
<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>

@ -172,12 +172,6 @@
<version>${revision}</version>
</dependency>
<!-- 接口请求参数加密模块 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-cryptapi</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
</dependencyManagement>

@ -1,34 +0,0 @@
<?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>

@ -1,15 +0,0 @@
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 {
}

@ -1,47 +0,0 @@
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;
}
}

@ -1,57 +0,0 @@
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);
}
}

@ -1,35 +0,0 @@
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;
}

@ -1,52 +0,0 @@
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);
}
}

@ -1,13 +0,0 @@
package org.dromara.cryptapi.enums;
public enum EncodeType {
/**
* base64
*/
BASE64,
/**
* 16
*/
HEX
}

@ -1,54 +0,0 @@
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() {
}
}

@ -1,55 +0,0 @@
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);
}
}

@ -0,0 +1,32 @@
package org.dromara.common.encrypt.config;
import jakarta.servlet.DispatcherType;
import org.dromara.common.encrypt.filter.CryptoFilter;
import org.dromara.common.encrypt.properties.ApiDecryptProperties;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
/**
* api
*
* @author wdhcr
*/
@AutoConfiguration
@EnableConfigurationProperties(ApiDecryptProperties.class)
@ConditionalOnProperty(value = "api-decrypt.enabled", havingValue = "true")
public class ApiDecryptAutoConfiguration {
@Bean
public FilterRegistrationBean<CryptoFilter> cryptoFilterRegistration(ApiDecryptProperties properties) {
FilterRegistrationBean<CryptoFilter> registration = new FilterRegistrationBean<>();
registration.setDispatcherTypes(DispatcherType.REQUEST);
registration.setFilter(new CryptoFilter(properties));
registration.addUrlPatterns("/*");
registration.setName("cryptoFilter");
registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
return registration;
}
}

@ -0,0 +1,48 @@
package org.dromara.common.encrypt.filter;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.encrypt.properties.ApiDecryptProperties;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import java.io.IOException;
import java.util.Objects;
/**
* Crypto
*
* @author wdhcr
*/
public class CryptoFilter implements Filter {
private final ApiDecryptProperties properties;
public CryptoFilter(ApiDecryptProperties properties) {
this.properties = properties;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
ServletRequest requestWrapper = null;
HttpServletRequest servletRequest = (HttpServletRequest) request;
// 是否为 json 请求
if (StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) {
// 是否为 put 或者 post 请求
if (HttpMethod.PUT.matches(servletRequest.getMethod()) || HttpMethod.POST.matches(servletRequest.getMethod())) {
// 是否存在加密标头
String headerValue = servletRequest.getHeader(properties.getHeaderFlag());
if (StringUtils.isNotBlank(headerValue)) {
requestWrapper = new DecryptRequestBodyWrapper(servletRequest, properties.getPublicKey(), properties.getPrivateKey(), properties.getHeaderFlag());
}
}
}
chain.doFilter(Objects.requireNonNullElse(requestWrapper, request), response);
}
@Override
public void destroy() {
}
}

@ -1,18 +1,12 @@
package org.dromara.cryptapi.filter;
package org.dromara.common.encrypt.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.dromara.common.encrypt.utils.EncryptUtils;
import org.springframework.http.MediaType;
import java.io.BufferedReader;
@ -30,21 +24,18 @@ public class DecryptRequestBodyWrapper extends HttpServletRequestWrapper {
private final byte[] body;
public DecryptRequestBodyWrapper(HttpServletRequest request, RsaEncryptor rsaEncryptor, String headerFlag) throws IOException {
public DecryptRequestBodyWrapper(HttpServletRequest request, String publicKey, String privateKey, 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)));
// 获取 AES 密码 采用 RSA 加密
String headerRsa = request.getHeader(headerFlag);
String decryptAes = EncryptUtils.decryptByRsa(headerRsa, privateKey);
// 解密 AES 密码
String aesPassword = EncryptUtils.decryptByBase64(decryptAes);
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);
String requestBody = new String(readBytes, StandardCharsets.UTF_8);
// 解密 body 采用 AES 加密
String decryptBody = EncryptUtils.decryptByAes(requestBody, aesPassword);
body = decryptBody.getBytes(StandardCharsets.UTF_8);
}

@ -1,4 +1,4 @@
package org.dromara.cryptapi.properties;
package org.dromara.common.encrypt.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ -14,7 +14,7 @@ public class ApiDecryptProperties {
/**
*
*/
private Boolean enable;
private Boolean enabled;
/**
*

@ -1 +1,3 @@
org.dromara.common.encrypt.config.EncryptorAutoConfiguration
org.dromara.common.encrypt.config.ApiDecryptAutoConfiguration

Loading…
Cancel
Save