From 2f419b0c53f3f112fcf88b59cc30d2758a50838f Mon Sep 17 00:00:00 2001 From: wdhcr Date: Wed, 21 Jun 2023 17:38:23 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=8E=A5=E5=8F=A3=E5=8A=A0?= =?UTF-8?q?=E5=AF=86=E5=BC=80=E5=90=AF=E6=96=B9=E5=BC=8F,=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=8D=95=E4=B8=AA=E6=8E=A5=E5=8F=A3=E5=BC=80=E5=90=AF?= =?UTF-8?q?,=20=E4=B9=9F=E6=94=AF=E6=8C=81=E5=85=A8=E5=B1=80=E5=BC=80?= =?UTF-8?q?=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));