From 6bc8d3841686e924d1a8ae4edc98806fa58df1f2 Mon Sep 17 00:00:00 2001 From: wdhcr Date: Tue, 20 Jun 2023 16:41:24 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E9=9B=86=E6=88=90=20jsencrypt=20=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E8=AF=B7=E6=B1=82=E5=8A=A0=E5=AF=86=E4=BC=A0=E8=BE=93?= =?UTF-8?q?,=20=E5=90=8E=E7=AB=AF=E5=8F=AF=E5=9C=A8application.yml?= =?UTF-8?q?=E4=B8=AD=E5=BC=80=E5=90=AF,=20=E5=89=8D=E7=AB=AF=E5=8F=AF?= =?UTF-8?q?=E5=9C=A8.env=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6IS=5FENCRYPT?= =?UTF-8?q?=E5=B1=9E=E6=80=A7=E4=BF=AE=E6=94=B9.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application.yml | 10 ++ .../com/ruoyi/common/filter/CryptoFilter.java | 60 ++++++++++ .../filter/DecryptRequestBodyWrapper.java | 106 ++++++++++++++++++ .../ruoyi/framework/config/FilterConfig.java | 23 ++++ .../properties/RequestEncryptProperties.java | 36 ++++++ ruoyi-ui-vue3/.env.development | 3 + ruoyi-ui-vue3/.env.production | 3 + ruoyi-ui-vue3/package.json | 1 + ruoyi-ui-vue3/src/utils/aes.js | 39 +++++++ ruoyi-ui-vue3/src/utils/request.js | 12 ++ ruoyi-ui/.env.development | 3 + ruoyi-ui/.env.production | 3 + ruoyi-ui/package.json | 1 + ruoyi-ui/src/utils/aes.js | 39 +++++++ ruoyi-ui/src/utils/request.js | 12 ++ 15 files changed, 351 insertions(+) create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/filter/CryptoFilter.java create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/filter/DecryptRequestBodyWrapper.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/RequestEncryptProperties.java create mode 100644 ruoyi-ui-vue3/src/utils/aes.js create mode 100644 ruoyi-ui/src/utils/aes.js diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index a6617bc6f..8b9ba89ef 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -196,6 +196,16 @@ mybatis-encryptor: publicKey: privateKey: +# 数据加密 +request-encryptor: + # 是否开启加密 + enable: true + # AES 加密头标识 + headerFlag: AES + # 公私钥 非对称算法的公私钥 如:SM2,RSA + publicKey: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ== + privateKey: MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKNPuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gAkM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWowcSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99EcvDQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthhYhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3UP8iWi1Qw0Y= + # Swagger配置 swagger: info: diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/filter/CryptoFilter.java b/ruoyi-common/src/main/java/com/ruoyi/common/filter/CryptoFilter.java new file mode 100644 index 000000000..ae9f749fd --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/filter/CryptoFilter.java @@ -0,0 +1,60 @@ +package com.ruoyi.common.filter; + +import com.ruoyi.common.encrypt.EncryptContext; +import com.ruoyi.common.encrypt.encryptor.RsaEncryptor; +import com.ruoyi.common.enums.AlgorithmType; +import com.ruoyi.common.utils.StringUtils; +import org.springframework.http.MediaType; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + +/** + * Crypto 过滤器 + * + * @author wdhcr + */ +public class CryptoFilter implements Filter { + + public static final String CRYPTO_ENABLE = "enable"; + 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 boolean enable; + private String headerFlag; + + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + String enableStr = filterConfig.getInitParameter(CryptoFilter.CRYPTO_ENABLE); + enable = Boolean.parseBoolean(enableStr); + EncryptContext encryptContext = new EncryptContext(); + encryptContext.setAlgorithm(AlgorithmType.RSA); + 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); + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + ServletRequest requestWrapper = null; + if (enable && request instanceof HttpServletRequest + && StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) { + requestWrapper = new DecryptRequestBodyWrapper((HttpServletRequest) request, rsaEncryptor, headerFlag); + } + if (null == requestWrapper) { + chain.doFilter(request, response); + } else { + chain.doFilter(requestWrapper, response); + } + } + + @Override + public void destroy() { + + } +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/filter/DecryptRequestBodyWrapper.java b/ruoyi-common/src/main/java/com/ruoyi/common/filter/DecryptRequestBodyWrapper.java new file mode 100644 index 000000000..5bb653b72 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/filter/DecryptRequestBodyWrapper.java @@ -0,0 +1,106 @@ +package com.ruoyi.common.filter; + +import cn.hutool.core.codec.Base64; +import cn.hutool.core.io.IoUtil; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.encrypt.EncryptContext; +import com.ruoyi.common.encrypt.encryptor.AesEncryptor; +import com.ruoyi.common.encrypt.encryptor.RsaEncryptor; +import com.ruoyi.common.enums.AlgorithmType; +import com.ruoyi.common.enums.EncodeType; +import com.ruoyi.common.exception.base.BaseException; +import com.ruoyi.common.utils.StringUtils; +import org.springframework.http.MediaType; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +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.setAlgorithm(AlgorithmType.AES); + 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) { + + } + }; + } +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java index 0770c2162..7738b8054 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java @@ -1,8 +1,10 @@ package com.ruoyi.framework.config; +import com.ruoyi.common.filter.CryptoFilter; import com.ruoyi.common.filter.RepeatableFilter; import com.ruoyi.common.filter.XssFilter; import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.config.properties.RequestEncryptProperties; import com.ruoyi.framework.config.properties.XssProperties; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -25,6 +27,27 @@ public class FilterConfig { @Autowired private XssProperties xssProperties; + @Autowired + private RequestEncryptProperties requestEncryptProperties; + + @Bean + @ConditionalOnProperty(value = "request-encryptor.enable", havingValue = "true") + public FilterRegistrationBean cryptoFilterRegistration() { + FilterRegistrationBean registration = new FilterRegistrationBean<>(); + registration.setDispatcherTypes(DispatcherType.REQUEST); + registration.setFilter(new CryptoFilter()); + registration.addUrlPatterns("/*"); + registration.setName("cryptoFilter"); + HashMap param = new HashMap<>(); + param.put(CryptoFilter.CRYPTO_ENABLE, String.valueOf(requestEncryptProperties.getEnable())); + param.put(CryptoFilter.CRYPTO_PUBLIC_KEY, requestEncryptProperties.getPublicKey()); + param.put(CryptoFilter.CRYPTO_PRIVATE_KEY, requestEncryptProperties.getPrivateKey()); + param.put(CryptoFilter.CRYPTO_HEADER_FLAG, requestEncryptProperties.getHeaderFlag()); + registration.setInitParameters(param); + registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE); + return registration; + } + @SuppressWarnings({"rawtypes", "unchecked"}) @Bean @ConditionalOnProperty(value = "xss.enabled", havingValue = "true") diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/RequestEncryptProperties.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/RequestEncryptProperties.java new file mode 100644 index 000000000..05b6ce390 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/properties/RequestEncryptProperties.java @@ -0,0 +1,36 @@ +package com.ruoyi.framework.config.properties; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 请求加解密属性配置类 + * @author wdhcr + */ +@Data +@Component +@ConfigurationProperties(prefix = "request-encryptor") +public class RequestEncryptProperties { + + /** + * 加密开关 + */ + private Boolean enable; + + /** + * 头部标识 + */ + private String headerFlag; + + + /** + * 公钥 + */ + private String publicKey; + + /** + * 私钥 + */ + private String privateKey; +} diff --git a/ruoyi-ui-vue3/.env.development b/ruoyi-ui-vue3/.env.development index ac4d2c13f..64e05213a 100644 --- a/ruoyi-ui-vue3/.env.development +++ b/ruoyi-ui-vue3/.env.development @@ -10,6 +10,9 @@ VITE_APP_BASE_API = '/dev-api' # 应用访问路径 例如使用前缀 /admin/ VITE_APP_CONTEXT_PATH = '/' +# 请求接口是否加密 +IS_ENCRYPT = 'true' + # 监控地址 VITE_APP_MONITRO_ADMIN = 'http://localhost:9090/admin/applications' diff --git a/ruoyi-ui-vue3/.env.production b/ruoyi-ui-vue3/.env.production index 18bb273cb..ebdc80f40 100644 --- a/ruoyi-ui-vue3/.env.production +++ b/ruoyi-ui-vue3/.env.production @@ -7,6 +7,9 @@ VITE_APP_ENV = 'production' # 应用访问路径 例如使用前缀 /admin/ VITE_APP_CONTEXT_PATH = '/' +# 请求接口是否加密 +IS_ENCRYPT = 'true' + # 监控地址 VITE_APP_MONITRO_ADMIN = '/admin/applications' diff --git a/ruoyi-ui-vue3/package.json b/ruoyi-ui-vue3/package.json index ad36a4f76..286cb2194 100644 --- a/ruoyi-ui-vue3/package.json +++ b/ruoyi-ui-vue3/package.json @@ -33,6 +33,7 @@ "devDependencies": { "@vitejs/plugin-vue": "3.1.0", "@vue/compiler-sfc": "3.2.45", + "crypto-js": "^4.1.1", "sass": "1.56.1", "unplugin-auto-import": "0.11.4", "vite": "3.2.3", diff --git a/ruoyi-ui-vue3/src/utils/aes.js b/ruoyi-ui-vue3/src/utils/aes.js new file mode 100644 index 000000000..e5633062a --- /dev/null +++ b/ruoyi-ui-vue3/src/utils/aes.js @@ -0,0 +1,39 @@ +import CryptoJS from 'crypto-js'; + +/** + * 随机生成aes 密钥 + * @returns {string} + */ +export const generateAesKey = () => { + return CryptoJS.enc.Utf8.parse(generateRandomString()); +}; + + +/** + * 使用密钥对数据进行加密 + * @param message + * @param aesKey + * @returns {string} + */ +export const encryptWithAes = (message, aesKey) => { + console.log(message, aesKey) + const encrypted = CryptoJS.AES.encrypt(message, aesKey, { + "mode": CryptoJS.mode.ECB, + "padding": CryptoJS.pad.Pkcs7 + }); + return encrypted.toString(); +}; + +/** + * 随机生成32位的字符串 + * @returns {string} + */ +const generateRandomString = () => { + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + let result = ''; + const charactersLength = characters.length; + for (let i = 0; i < 32; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; +}; diff --git a/ruoyi-ui-vue3/src/utils/request.js b/ruoyi-ui-vue3/src/utils/request.js index 1da35094e..3995e8f8e 100644 --- a/ruoyi-ui-vue3/src/utils/request.js +++ b/ruoyi-ui-vue3/src/utils/request.js @@ -6,6 +6,9 @@ import { tansParams, blobValidate } from '@/utils/ruoyi' import cache from '@/plugins/cache' import { saveAs } from 'file-saver' import useUserStore from '@/store/modules/user' +import {encryptWithAes, generateAesKey} from "@/utils/aes"; +import {encrypt} from "@/utils/jsencrypt"; +import CryptoJS from "crypto-js"; let downloadLoadingInstance; // 是否显示重新登录 @@ -28,6 +31,8 @@ service.interceptors.request.use(config => { const isToken = (config.headers || {}).isToken === false // 是否需要防止数据重复提交 const isRepeatSubmit = (config.headers || {}).repeatSubmit === false + // 是否需要加密 + const isEncrypt = (config.headers || {}).isEncrypt === import.meta.env.IS_ENCRYPT if (getToken() && !isToken) { config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 } @@ -61,6 +66,13 @@ service.interceptors.request.use(config => { } } } + // 当开启参数加密 + if (isEncrypt && (config.method === 'post' || config.method === 'put')) { + // 生成一个 AES 密钥 + const aesKey = generateAesKey(); + config.headers['AES'] = encrypt(aesKey.toString(CryptoJS.enc.Base64)); + config.data = typeof config.data === 'object' ? encryptWithAes(JSON.stringify(config.data), aesKey) : encryptWithAes(config.data, aesKey); + } return config }, error => { console.log(error) diff --git a/ruoyi-ui/.env.development b/ruoyi-ui/.env.development index c5457c9a6..b9aa0d405 100644 --- a/ruoyi-ui/.env.development +++ b/ruoyi-ui/.env.development @@ -7,6 +7,9 @@ ENV = 'development' # 若依管理系统/开发环境 VUE_APP_BASE_API = '/dev-api' +# 请求接口是否加密 +IS_ENCRYPT = 'true' + # 应用访问路径 例如使用前缀 /admin/ VUE_APP_CONTEXT_PATH = '/' diff --git a/ruoyi-ui/.env.production b/ruoyi-ui/.env.production index 1d2cbae6a..5a72452a1 100644 --- a/ruoyi-ui/.env.production +++ b/ruoyi-ui/.env.production @@ -7,6 +7,9 @@ ENV = 'production' # 若依管理系统/生产环境 VUE_APP_BASE_API = '/prod-api' +# 请求接口是否加密 +IS_ENCRYPT = 'true' + # 应用访问路径 例如使用前缀 /admin/ VUE_APP_CONTEXT_PATH = '/' diff --git a/ruoyi-ui/package.json b/ruoyi-ui/package.json index 16924f395..2cef4f922 100644 --- a/ruoyi-ui/package.json +++ b/ruoyi-ui/package.json @@ -68,6 +68,7 @@ "chalk": "4.1.0", "compression-webpack-plugin": "5.0.2", "connect": "3.6.6", + "crypto-js": "^4.1.1", "eslint": "7.15.0", "eslint-plugin-vue": "7.2.0", "lint-staged": "10.5.3", diff --git a/ruoyi-ui/src/utils/aes.js b/ruoyi-ui/src/utils/aes.js new file mode 100644 index 000000000..e5633062a --- /dev/null +++ b/ruoyi-ui/src/utils/aes.js @@ -0,0 +1,39 @@ +import CryptoJS from 'crypto-js'; + +/** + * 随机生成aes 密钥 + * @returns {string} + */ +export const generateAesKey = () => { + return CryptoJS.enc.Utf8.parse(generateRandomString()); +}; + + +/** + * 使用密钥对数据进行加密 + * @param message + * @param aesKey + * @returns {string} + */ +export const encryptWithAes = (message, aesKey) => { + console.log(message, aesKey) + const encrypted = CryptoJS.AES.encrypt(message, aesKey, { + "mode": CryptoJS.mode.ECB, + "padding": CryptoJS.pad.Pkcs7 + }); + return encrypted.toString(); +}; + +/** + * 随机生成32位的字符串 + * @returns {string} + */ +const generateRandomString = () => { + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + let result = ''; + const charactersLength = characters.length; + for (let i = 0; i < 32; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; +}; diff --git a/ruoyi-ui/src/utils/request.js b/ruoyi-ui/src/utils/request.js index 850685cd6..1c2ebe376 100644 --- a/ruoyi-ui/src/utils/request.js +++ b/ruoyi-ui/src/utils/request.js @@ -6,6 +6,9 @@ import errorCode from '@/utils/errorCode' import { tansParams, blobValidate } from "@/utils/ruoyi"; import cache from '@/plugins/cache' import { saveAs } from 'file-saver' +import {encryptWithAes, generateAesKey} from "@/utils/aes"; +import {encrypt} from "@/utils/jsencrypt"; +import CryptoJS from "crypto-js"; let downloadLoadingInstance; // 是否显示重新登录 @@ -31,6 +34,8 @@ service.interceptors.request.use(config => { if (getToken() && !isToken) { config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 } + // 是否需要加密 + const isEncrypt = (config.headers || {}).isEncrypt === process.env.IS_ENCRYPT // get请求映射params参数 if (config.method === 'get' && config.params) { let url = config.url + '?' + tansParams(config.params); @@ -61,6 +66,13 @@ service.interceptors.request.use(config => { } } } + // 当开启参数加密 + if (isEncrypt && (config.method === 'post' || config.method === 'put')) { + // 生成一个 AES 密钥 + const aesKey = generateAesKey(); + config.headers['AES'] = encrypt(aesKey.toString(CryptoJS.enc.Base64)); + config.data = typeof config.data === 'object' ? encryptWithAes(JSON.stringify(config.data), aesKey) : encryptWithAes(config.data, aesKey); + } return config }, error => { console.log(error) From 5ae176305e9e8070e70c028d0e6e7a483cec6349 Mon Sep 17 00:00:00 2001 From: wdhcr Date: Tue, 20 Jun 2023 16:44:46 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E9=9B=86=E6=88=90=20jsencrypt=20=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E8=AF=B7=E6=B1=82=E5=8A=A0=E5=AF=86=E4=BC=A0=E8=BE=93?= =?UTF-8?q?,=20=E5=90=8E=E7=AB=AF=E5=8F=AF=E5=9C=A8application.yml?= =?UTF-8?q?=E4=B8=AD=E5=BC=80=E5=90=AF,=20=E5=89=8D=E7=AB=AF=E5=8F=AF?= =?UTF-8?q?=E5=9C=A8.env=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6IS=5FENCRYPT?= =?UTF-8?q?=E5=B1=9E=E6=80=A7=E4=BF=AE=E6=94=B9.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-ui-vue3/src/utils/aes.js | 1 - ruoyi-ui/src/utils/aes.js | 1 - 2 files changed, 2 deletions(-) diff --git a/ruoyi-ui-vue3/src/utils/aes.js b/ruoyi-ui-vue3/src/utils/aes.js index e5633062a..d06da6fbe 100644 --- a/ruoyi-ui-vue3/src/utils/aes.js +++ b/ruoyi-ui-vue3/src/utils/aes.js @@ -16,7 +16,6 @@ export const generateAesKey = () => { * @returns {string} */ export const encryptWithAes = (message, aesKey) => { - console.log(message, aesKey) const encrypted = CryptoJS.AES.encrypt(message, aesKey, { "mode": CryptoJS.mode.ECB, "padding": CryptoJS.pad.Pkcs7 diff --git a/ruoyi-ui/src/utils/aes.js b/ruoyi-ui/src/utils/aes.js index e5633062a..d06da6fbe 100644 --- a/ruoyi-ui/src/utils/aes.js +++ b/ruoyi-ui/src/utils/aes.js @@ -16,7 +16,6 @@ export const generateAesKey = () => { * @returns {string} */ export const encryptWithAes = (message, aesKey) => { - console.log(message, aesKey) const encrypted = CryptoJS.AES.encrypt(message, aesKey, { "mode": CryptoJS.mode.ECB, "padding": CryptoJS.pad.Pkcs7 From 76c196dbec6fb9bfebae760fd7489a14e980856a Mon Sep 17 00:00:00 2001 From: wdhcr Date: Tue, 20 Jun 2023 17:48:35 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E6=98=AF=E5=90=A6=E5=8A=A0=E5=AF=86,=20?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E4=B8=BA=E4=B8=8D=E5=8A=A0=E5=AF=86.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-admin/src/main/resources/application.yml | 2 +- ruoyi-ui-vue3/.env.development | 2 +- ruoyi-ui-vue3/.env.production | 2 +- ruoyi-ui-vue3/src/utils/request.js | 4 +--- ruoyi-ui/.env.development | 2 +- ruoyi-ui/.env.production | 2 +- ruoyi-ui/src/utils/request.js | 5 ++--- 7 files changed, 8 insertions(+), 11 deletions(-) diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index 8b9ba89ef..3e2aabbed 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -199,7 +199,7 @@ mybatis-encryptor: # 数据加密 request-encryptor: # 是否开启加密 - enable: true + enable: false # AES 加密头标识 headerFlag: AES # 公私钥 非对称算法的公私钥 如:SM2,RSA diff --git a/ruoyi-ui-vue3/.env.development b/ruoyi-ui-vue3/.env.development index 64e05213a..dd3b79499 100644 --- a/ruoyi-ui-vue3/.env.development +++ b/ruoyi-ui-vue3/.env.development @@ -11,7 +11,7 @@ VITE_APP_BASE_API = '/dev-api' VITE_APP_CONTEXT_PATH = '/' # 请求接口是否加密 -IS_ENCRYPT = 'true' +VITE_APP_IS_ENCRYPT = false # 监控地址 VITE_APP_MONITRO_ADMIN = 'http://localhost:9090/admin/applications' diff --git a/ruoyi-ui-vue3/.env.production b/ruoyi-ui-vue3/.env.production index ebdc80f40..3e5f09a41 100644 --- a/ruoyi-ui-vue3/.env.production +++ b/ruoyi-ui-vue3/.env.production @@ -8,7 +8,7 @@ VITE_APP_ENV = 'production' VITE_APP_CONTEXT_PATH = '/' # 请求接口是否加密 -IS_ENCRYPT = 'true' +VITE_APP_IS_ENCRYPT = false # 监控地址 VITE_APP_MONITRO_ADMIN = '/admin/applications' diff --git a/ruoyi-ui-vue3/src/utils/request.js b/ruoyi-ui-vue3/src/utils/request.js index 3995e8f8e..255feb070 100644 --- a/ruoyi-ui-vue3/src/utils/request.js +++ b/ruoyi-ui-vue3/src/utils/request.js @@ -31,8 +31,6 @@ service.interceptors.request.use(config => { const isToken = (config.headers || {}).isToken === false // 是否需要防止数据重复提交 const isRepeatSubmit = (config.headers || {}).repeatSubmit === false - // 是否需要加密 - const isEncrypt = (config.headers || {}).isEncrypt === import.meta.env.IS_ENCRYPT if (getToken() && !isToken) { config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 } @@ -67,7 +65,7 @@ service.interceptors.request.use(config => { } } // 当开启参数加密 - if (isEncrypt && (config.method === 'post' || config.method === 'put')) { + if (import.meta.env.VITE_APP_IS_ENCRYPT === 'true' && (config.method === 'post' || config.method === 'put')) { // 生成一个 AES 密钥 const aesKey = generateAesKey(); config.headers['AES'] = encrypt(aesKey.toString(CryptoJS.enc.Base64)); diff --git a/ruoyi-ui/.env.development b/ruoyi-ui/.env.development index b9aa0d405..06160f864 100644 --- a/ruoyi-ui/.env.development +++ b/ruoyi-ui/.env.development @@ -8,7 +8,7 @@ ENV = 'development' VUE_APP_BASE_API = '/dev-api' # 请求接口是否加密 -IS_ENCRYPT = 'true' +VUE_APP_IS_ENCRYPT = false # 应用访问路径 例如使用前缀 /admin/ VUE_APP_CONTEXT_PATH = '/' diff --git a/ruoyi-ui/.env.production b/ruoyi-ui/.env.production index 5a72452a1..d66332bf6 100644 --- a/ruoyi-ui/.env.production +++ b/ruoyi-ui/.env.production @@ -8,7 +8,7 @@ ENV = 'production' VUE_APP_BASE_API = '/prod-api' # 请求接口是否加密 -IS_ENCRYPT = 'true' +VUE_APP_IS_ENCRYPT = false # 应用访问路径 例如使用前缀 /admin/ VUE_APP_CONTEXT_PATH = '/' diff --git a/ruoyi-ui/src/utils/request.js b/ruoyi-ui/src/utils/request.js index 1c2ebe376..49718e034 100644 --- a/ruoyi-ui/src/utils/request.js +++ b/ruoyi-ui/src/utils/request.js @@ -34,8 +34,7 @@ service.interceptors.request.use(config => { if (getToken() && !isToken) { config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 } - // 是否需要加密 - const isEncrypt = (config.headers || {}).isEncrypt === process.env.IS_ENCRYPT + // get请求映射params参数 if (config.method === 'get' && config.params) { let url = config.url + '?' + tansParams(config.params); @@ -67,7 +66,7 @@ service.interceptors.request.use(config => { } } // 当开启参数加密 - if (isEncrypt && (config.method === 'post' || config.method === 'put')) { + if (process.env.VUE_APP_IS_ENCRYPT === 'true' && (config.method === 'post' || config.method === 'put')) { // 生成一个 AES 密钥 const aesKey = generateAesKey(); config.headers['AES'] = encrypt(aesKey.toString(CryptoJS.enc.Base64)); From 2f419b0c53f3f112fcf88b59cc30d2758a50838f Mon Sep 17 00:00:00 2001 From: wdhcr Date: Wed, 21 Jun 2023 17:38:23 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E5=8A=A0=E5=AF=86=E5=BC=80=E5=90=AF=E6=96=B9=E5=BC=8F,=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=8D=95=E4=B8=AA=E6=8E=A5=E5=8F=A3=E5=BC=80?= =?UTF-8?q?=E5=90=AF,=20=E4=B9=9F=E6=94=AF=E6=8C=81=E5=85=A8=E5=B1=80?= =?UTF-8?q?=E5=BC=80=E5=90=AF.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ruoyi/common/annotation/Decrypt.java | 15 +++++ .../com/ruoyi/common/filter/CryptoFilter.java | 18 +++--- .../ruoyi/framework/config/FilterConfig.java | 20 +++++-- .../framework/handler/DecryptUrlHandler.java | 56 +++++++++++++++++++ ruoyi-ui-vue3/src/utils/request.js | 4 +- ruoyi-ui/src/utils/request.js | 4 +- 6 files changed, 100 insertions(+), 17 deletions(-) create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/annotation/Decrypt.java create mode 100644 ruoyi-framework/src/main/java/com/ruoyi/framework/handler/DecryptUrlHandler.java diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Decrypt.java b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Decrypt.java new file mode 100644 index 000000000..5158e405e --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/annotation/Decrypt.java @@ -0,0 +1,15 @@ +package com.ruoyi.common.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 Decrypt { +} diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/filter/CryptoFilter.java b/ruoyi-common/src/main/java/com/ruoyi/common/filter/CryptoFilter.java index ae9f749fd..486d4e337 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/filter/CryptoFilter.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/filter/CryptoFilter.java @@ -4,11 +4,12 @@ import com.ruoyi.common.encrypt.EncryptContext; import com.ruoyi.common.encrypt.encryptor.RsaEncryptor; import com.ruoyi.common.enums.AlgorithmType; import com.ruoyi.common.utils.StringUtils; +import lombok.SneakyThrows; +import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; -import java.io.IOException; /** * Crypto 过滤器 @@ -17,19 +18,15 @@ import java.io.IOException; */ public class CryptoFilter implements Filter { - public static final String CRYPTO_ENABLE = "enable"; 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 boolean enable; private String headerFlag; @Override public void init(FilterConfig filterConfig) throws ServletException { - String enableStr = filterConfig.getInitParameter(CryptoFilter.CRYPTO_ENABLE); - enable = Boolean.parseBoolean(enableStr); EncryptContext encryptContext = new EncryptContext(); encryptContext.setAlgorithm(AlgorithmType.RSA); encryptContext.setPublicKey(filterConfig.getInitParameter(CryptoFilter.CRYPTO_PUBLIC_KEY)); @@ -38,13 +35,14 @@ public class CryptoFilter implements Filter { rsaEncryptor = new RsaEncryptor(encryptContext); } + @SneakyThrows @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { ServletRequest requestWrapper = null; - if (enable && request instanceof HttpServletRequest - && StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) { - requestWrapper = new DecryptRequestBodyWrapper((HttpServletRequest) request, rsaEncryptor, headerFlag); + 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); } if (null == requestWrapper) { chain.doFilter(request, response); diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java index 7738b8054..925aebf0f 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/config/FilterConfig.java @@ -1,11 +1,13 @@ package com.ruoyi.framework.config; +import cn.hutool.core.collection.CollectionUtil; import com.ruoyi.common.filter.CryptoFilter; import com.ruoyi.common.filter.RepeatableFilter; import com.ruoyi.common.filter.XssFilter; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.framework.config.properties.RequestEncryptProperties; import com.ruoyi.framework.config.properties.XssProperties; +import com.ruoyi.framework.handler.DecryptUrlHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.web.servlet.FilterRegistrationBean; @@ -14,6 +16,7 @@ import org.springframework.context.annotation.Configuration; import javax.servlet.DispatcherType; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -30,16 +33,23 @@ public class FilterConfig { @Autowired private RequestEncryptProperties requestEncryptProperties; + @Autowired + private DecryptUrlHandler decryptUrlHandler; + @Bean - @ConditionalOnProperty(value = "request-encryptor.enable", havingValue = "true") public FilterRegistrationBean cryptoFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean<>(); registration.setDispatcherTypes(DispatcherType.REQUEST); registration.setFilter(new CryptoFilter()); - registration.addUrlPatterns("/*"); + List urls = decryptUrlHandler.getUrls(); + if (CollectionUtil.isNotEmpty(urls) || requestEncryptProperties.getEnable()) { + registration.setEnabled(true); + registration.addUrlPatterns(urls.toArray(new String[0])); + } else { + registration.setEnabled(false); + } registration.setName("cryptoFilter"); HashMap param = new HashMap<>(); - param.put(CryptoFilter.CRYPTO_ENABLE, String.valueOf(requestEncryptProperties.getEnable())); param.put(CryptoFilter.CRYPTO_PUBLIC_KEY, requestEncryptProperties.getPublicKey()); param.put(CryptoFilter.CRYPTO_PRIVATE_KEY, requestEncryptProperties.getPrivateKey()); param.put(CryptoFilter.CRYPTO_HEADER_FLAG, requestEncryptProperties.getHeaderFlag()); @@ -57,8 +67,8 @@ public class FilterConfig { registration.setFilter(new XssFilter()); registration.addUrlPatterns(StringUtils.split(xssProperties.getUrlPatterns(), StringUtils.SEPARATOR)); registration.setName("xssFilter"); - registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE); - Map initParameters = new HashMap(); + registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE + 1); + Map initParameters = new HashMap<>(); initParameters.put("excludes", xssProperties.getExcludes()); registration.setInitParameters(initParameters); return registration; diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/handler/DecryptUrlHandler.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/handler/DecryptUrlHandler.java new file mode 100644 index 000000000..0c1064b16 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/handler/DecryptUrlHandler.java @@ -0,0 +1,56 @@ +package com.ruoyi.framework.handler; + +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ReUtil; +import com.ruoyi.common.annotation.Decrypt; +import lombok.Data; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +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; +import java.util.stream.Collectors; + +/** + * 获取需要解密的Url配置 + * + * @author wdhcr + */ +@Data +@Component +public class DecryptUrlHandler implements InitializingBean { + + private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}"); + + private List urls = new ArrayList<>(); + + @Autowired + private RequestMappingHandlerMapping requestMappingHandlerMapping; + + @Override + public void afterPropertiesSet() { + Set set = new HashSet<>(); + Map map = requestMappingHandlerMapping.getHandlerMethods(); + List requestMappingInfos = map.entrySet().stream().filter(item -> { + HandlerMethod method = item.getValue(); + Decrypt decrypt = method.getMethodAnnotation(Decrypt.class); + // 标有解密注解的并且是post 或者put 请求的handler + return decrypt != null && CollectionUtil.containsAny(item.getKey().getMethodsCondition().getMethods(), Arrays.asList(RequestMethod.PUT, RequestMethod.POST)); + }).map(Map.Entry::getKey).collect(Collectors.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); + } + +} diff --git a/ruoyi-ui-vue3/src/utils/request.js b/ruoyi-ui-vue3/src/utils/request.js index 255feb070..7510b777f 100644 --- a/ruoyi-ui-vue3/src/utils/request.js +++ b/ruoyi-ui-vue3/src/utils/request.js @@ -31,6 +31,8 @@ service.interceptors.request.use(config => { const isToken = (config.headers || {}).isToken === false // 是否需要防止数据重复提交 const isRepeatSubmit = (config.headers || {}).repeatSubmit === false + // 是否需要加密 + const isEncrypt = (config.headers || {}).isEncrypt === true || import.meta.env.VITE_APP_IS_ENCRYPT === 'true' if (getToken() && !isToken) { config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 } @@ -65,7 +67,7 @@ service.interceptors.request.use(config => { } } // 当开启参数加密 - if (import.meta.env.VITE_APP_IS_ENCRYPT === 'true' && (config.method === 'post' || config.method === 'put')) { + if (isEncrypt && (config.method === 'post' || config.method === 'put')) { // 生成一个 AES 密钥 const aesKey = generateAesKey(); config.headers['AES'] = encrypt(aesKey.toString(CryptoJS.enc.Base64)); diff --git a/ruoyi-ui/src/utils/request.js b/ruoyi-ui/src/utils/request.js index 49718e034..7949faa30 100644 --- a/ruoyi-ui/src/utils/request.js +++ b/ruoyi-ui/src/utils/request.js @@ -31,6 +31,8 @@ service.interceptors.request.use(config => { const isToken = (config.headers || {}).isToken === false // 是否需要防止数据重复提交 const isRepeatSubmit = (config.headers || {}).repeatSubmit === false + // 是否需要加密 + const isEncrypt = (config.headers || {}).isEncrypt === true || process.env.VUE_APP_IS_ENCRYPT === 'true' if (getToken() && !isToken) { config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 } @@ -66,7 +68,7 @@ service.interceptors.request.use(config => { } } // 当开启参数加密 - if (process.env.VUE_APP_IS_ENCRYPT === 'true' && (config.method === 'post' || config.method === 'put')) { + if (isEncrypt && (config.method === 'post' || config.method === 'put')) { // 生成一个 AES 密钥 const aesKey = generateAesKey(); config.headers['AES'] = encrypt(aesKey.toString(CryptoJS.enc.Base64));