mirror of
https://gitee.com/dromara/RuoYi-Vue-Plus.git
synced 2026-09-21 18:45:57 +08:00
Pre Merge pull request !375 from JackWhh/jsencrypt
This commit is contained in:
commit
997a999990
@ -196,6 +196,16 @@ mybatis-encryptor:
|
|||||||
publicKey:
|
publicKey:
|
||||||
privateKey:
|
privateKey:
|
||||||
|
|
||||||
|
# 数据加密
|
||||||
|
request-encryptor:
|
||||||
|
# 是否开启加密
|
||||||
|
enable: false
|
||||||
|
# AES 加密头标识
|
||||||
|
headerFlag: AES
|
||||||
|
# 公私钥 非对称算法的公私钥 如:SM2,RSA
|
||||||
|
publicKey: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==
|
||||||
|
privateKey: MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKNPuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gAkM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWowcSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99EcvDQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthhYhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3UP8iWi1Qw0Y=
|
||||||
|
|
||||||
# Swagger配置
|
# Swagger配置
|
||||||
swagger:
|
swagger:
|
||||||
info:
|
info:
|
||||||
|
|||||||
@ -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 {
|
||||||
|
}
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
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 lombok.SneakyThrows;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
|
||||||
|
import javax.servlet.*;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) throws ServletException {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
if (null == requestWrapper) {
|
||||||
|
chain.doFilter(request, response);
|
||||||
|
} else {
|
||||||
|
chain.doFilter(requestWrapper, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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) {
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,9 +1,13 @@
|
|||||||
package com.ruoyi.framework.config;
|
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.RepeatableFilter;
|
||||||
import com.ruoyi.common.filter.XssFilter;
|
import com.ruoyi.common.filter.XssFilter;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
import com.ruoyi.framework.config.properties.RequestEncryptProperties;
|
||||||
import com.ruoyi.framework.config.properties.XssProperties;
|
import com.ruoyi.framework.config.properties.XssProperties;
|
||||||
|
import com.ruoyi.framework.handler.DecryptUrlHandler;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||||
@ -12,6 +16,7 @@ import org.springframework.context.annotation.Configuration;
|
|||||||
|
|
||||||
import javax.servlet.DispatcherType;
|
import javax.servlet.DispatcherType;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -25,6 +30,34 @@ public class FilterConfig {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private XssProperties xssProperties;
|
private XssProperties xssProperties;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RequestEncryptProperties requestEncryptProperties;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private DecryptUrlHandler decryptUrlHandler;
|
||||||
|
|
||||||
|
@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) || requestEncryptProperties.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, 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"})
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(value = "xss.enabled", havingValue = "true")
|
@ConditionalOnProperty(value = "xss.enabled", havingValue = "true")
|
||||||
@ -34,8 +67,8 @@ public class FilterConfig {
|
|||||||
registration.setFilter(new XssFilter());
|
registration.setFilter(new XssFilter());
|
||||||
registration.addUrlPatterns(StringUtils.split(xssProperties.getUrlPatterns(), StringUtils.SEPARATOR));
|
registration.addUrlPatterns(StringUtils.split(xssProperties.getUrlPatterns(), StringUtils.SEPARATOR));
|
||||||
registration.setName("xssFilter");
|
registration.setName("xssFilter");
|
||||||
registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
|
registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE + 1);
|
||||||
Map<String, String> initParameters = new HashMap<String, String>();
|
Map<String, String> initParameters = new HashMap<>();
|
||||||
initParameters.put("excludes", xssProperties.getExcludes());
|
initParameters.put("excludes", xssProperties.getExcludes());
|
||||||
registration.setInitParameters(initParameters);
|
registration.setInitParameters(initParameters);
|
||||||
return registration;
|
return registration;
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
@ -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<String> urls = new ArrayList<>();
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private 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();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -10,6 +10,9 @@ VITE_APP_BASE_API = '/dev-api'
|
|||||||
# 应用访问路径 例如使用前缀 /admin/
|
# 应用访问路径 例如使用前缀 /admin/
|
||||||
VITE_APP_CONTEXT_PATH = '/'
|
VITE_APP_CONTEXT_PATH = '/'
|
||||||
|
|
||||||
|
# 请求接口是否加密
|
||||||
|
VITE_APP_IS_ENCRYPT = false
|
||||||
|
|
||||||
# 监控地址
|
# 监控地址
|
||||||
VITE_APP_MONITRO_ADMIN = 'http://localhost:9090/admin/applications'
|
VITE_APP_MONITRO_ADMIN = 'http://localhost:9090/admin/applications'
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,9 @@ VITE_APP_ENV = 'production'
|
|||||||
# 应用访问路径 例如使用前缀 /admin/
|
# 应用访问路径 例如使用前缀 /admin/
|
||||||
VITE_APP_CONTEXT_PATH = '/'
|
VITE_APP_CONTEXT_PATH = '/'
|
||||||
|
|
||||||
|
# 请求接口是否加密
|
||||||
|
VITE_APP_IS_ENCRYPT = false
|
||||||
|
|
||||||
# 监控地址
|
# 监控地址
|
||||||
VITE_APP_MONITRO_ADMIN = '/admin/applications'
|
VITE_APP_MONITRO_ADMIN = '/admin/applications'
|
||||||
|
|
||||||
|
|||||||
@ -33,6 +33,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "3.1.0",
|
"@vitejs/plugin-vue": "3.1.0",
|
||||||
"@vue/compiler-sfc": "3.2.45",
|
"@vue/compiler-sfc": "3.2.45",
|
||||||
|
"crypto-js": "^4.1.1",
|
||||||
"sass": "1.56.1",
|
"sass": "1.56.1",
|
||||||
"unplugin-auto-import": "0.11.4",
|
"unplugin-auto-import": "0.11.4",
|
||||||
"vite": "3.2.3",
|
"vite": "3.2.3",
|
||||||
|
|||||||
38
ruoyi-ui-vue3/src/utils/aes.js
Normal file
38
ruoyi-ui-vue3/src/utils/aes.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
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) => {
|
||||||
|
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;
|
||||||
|
};
|
||||||
@ -6,6 +6,9 @@ import { tansParams, blobValidate } from '@/utils/ruoyi'
|
|||||||
import cache from '@/plugins/cache'
|
import cache from '@/plugins/cache'
|
||||||
import { saveAs } from 'file-saver'
|
import { saveAs } from 'file-saver'
|
||||||
import useUserStore from '@/store/modules/user'
|
import useUserStore from '@/store/modules/user'
|
||||||
|
import {encryptWithAes, generateAesKey} from "@/utils/aes";
|
||||||
|
import {encrypt} from "@/utils/jsencrypt";
|
||||||
|
import CryptoJS from "crypto-js";
|
||||||
|
|
||||||
let downloadLoadingInstance;
|
let downloadLoadingInstance;
|
||||||
// 是否显示重新登录
|
// 是否显示重新登录
|
||||||
@ -28,6 +31,8 @@ service.interceptors.request.use(config => {
|
|||||||
const isToken = (config.headers || {}).isToken === false
|
const isToken = (config.headers || {}).isToken === false
|
||||||
// 是否需要防止数据重复提交
|
// 是否需要防止数据重复提交
|
||||||
const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
|
const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
|
||||||
|
// 是否需要加密
|
||||||
|
const isEncrypt = (config.headers || {}).isEncrypt === true || import.meta.env.VITE_APP_IS_ENCRYPT === 'true'
|
||||||
if (getToken() && !isToken) {
|
if (getToken() && !isToken) {
|
||||||
config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
|
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
|
return config
|
||||||
}, error => {
|
}, error => {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
|
|||||||
@ -7,6 +7,9 @@ ENV = 'development'
|
|||||||
# 若依管理系统/开发环境
|
# 若依管理系统/开发环境
|
||||||
VUE_APP_BASE_API = '/dev-api'
|
VUE_APP_BASE_API = '/dev-api'
|
||||||
|
|
||||||
|
# 请求接口是否加密
|
||||||
|
VUE_APP_IS_ENCRYPT = false
|
||||||
|
|
||||||
# 应用访问路径 例如使用前缀 /admin/
|
# 应用访问路径 例如使用前缀 /admin/
|
||||||
VUE_APP_CONTEXT_PATH = '/'
|
VUE_APP_CONTEXT_PATH = '/'
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,9 @@ ENV = 'production'
|
|||||||
# 若依管理系统/生产环境
|
# 若依管理系统/生产环境
|
||||||
VUE_APP_BASE_API = '/prod-api'
|
VUE_APP_BASE_API = '/prod-api'
|
||||||
|
|
||||||
|
# 请求接口是否加密
|
||||||
|
VUE_APP_IS_ENCRYPT = false
|
||||||
|
|
||||||
# 应用访问路径 例如使用前缀 /admin/
|
# 应用访问路径 例如使用前缀 /admin/
|
||||||
VUE_APP_CONTEXT_PATH = '/'
|
VUE_APP_CONTEXT_PATH = '/'
|
||||||
|
|
||||||
|
|||||||
@ -68,6 +68,7 @@
|
|||||||
"chalk": "4.1.0",
|
"chalk": "4.1.0",
|
||||||
"compression-webpack-plugin": "5.0.2",
|
"compression-webpack-plugin": "5.0.2",
|
||||||
"connect": "3.6.6",
|
"connect": "3.6.6",
|
||||||
|
"crypto-js": "^4.1.1",
|
||||||
"eslint": "7.15.0",
|
"eslint": "7.15.0",
|
||||||
"eslint-plugin-vue": "7.2.0",
|
"eslint-plugin-vue": "7.2.0",
|
||||||
"lint-staged": "10.5.3",
|
"lint-staged": "10.5.3",
|
||||||
|
|||||||
38
ruoyi-ui/src/utils/aes.js
Normal file
38
ruoyi-ui/src/utils/aes.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
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) => {
|
||||||
|
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;
|
||||||
|
};
|
||||||
@ -6,6 +6,9 @@ import errorCode from '@/utils/errorCode'
|
|||||||
import { tansParams, blobValidate } from "@/utils/ruoyi";
|
import { tansParams, blobValidate } from "@/utils/ruoyi";
|
||||||
import cache from '@/plugins/cache'
|
import cache from '@/plugins/cache'
|
||||||
import { saveAs } from 'file-saver'
|
import { saveAs } from 'file-saver'
|
||||||
|
import {encryptWithAes, generateAesKey} from "@/utils/aes";
|
||||||
|
import {encrypt} from "@/utils/jsencrypt";
|
||||||
|
import CryptoJS from "crypto-js";
|
||||||
|
|
||||||
let downloadLoadingInstance;
|
let downloadLoadingInstance;
|
||||||
// 是否显示重新登录
|
// 是否显示重新登录
|
||||||
@ -28,9 +31,12 @@ service.interceptors.request.use(config => {
|
|||||||
const isToken = (config.headers || {}).isToken === false
|
const isToken = (config.headers || {}).isToken === false
|
||||||
// 是否需要防止数据重复提交
|
// 是否需要防止数据重复提交
|
||||||
const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
|
const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
|
||||||
|
// 是否需要加密
|
||||||
|
const isEncrypt = (config.headers || {}).isEncrypt === true || process.env.VUE_APP_IS_ENCRYPT === 'true'
|
||||||
if (getToken() && !isToken) {
|
if (getToken() && !isToken) {
|
||||||
config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
|
config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
|
||||||
}
|
}
|
||||||
|
|
||||||
// get请求映射params参数
|
// get请求映射params参数
|
||||||
if (config.method === 'get' && config.params) {
|
if (config.method === 'get' && config.params) {
|
||||||
let url = config.url + '?' + tansParams(config.params);
|
let url = config.url + '?' + tansParams(config.params);
|
||||||
@ -61,6 +67,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
|
return config
|
||||||
}, error => {
|
}, error => {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user