修改接口加密开启方式, 支持单个接口开启, 也支持全局开启.

This commit is contained in:
wdhcr 2023-06-21 17:38:23 +08:00
parent 76c196dbec
commit 2f419b0c53
6 changed files with 100 additions and 17 deletions

View File

@ -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 {
}

View File

@ -4,11 +4,12 @@ import com.ruoyi.common.encrypt.EncryptContext;
import com.ruoyi.common.encrypt.encryptor.RsaEncryptor; import com.ruoyi.common.encrypt.encryptor.RsaEncryptor;
import com.ruoyi.common.enums.AlgorithmType; import com.ruoyi.common.enums.AlgorithmType;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import lombok.SneakyThrows;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import javax.servlet.*; import javax.servlet.*;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/** /**
* Crypto 过滤器 * Crypto 过滤器
@ -17,19 +18,15 @@ import java.io.IOException;
*/ */
public class CryptoFilter implements Filter { 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_PUBLIC_KEY = "publicKey";
public static final String CRYPTO_PRIVATE_KEY = "privateKey"; public static final String CRYPTO_PRIVATE_KEY = "privateKey";
public static final String CRYPTO_HEADER_FLAG = "headerFlag"; public static final String CRYPTO_HEADER_FLAG = "headerFlag";
private RsaEncryptor rsaEncryptor; private RsaEncryptor rsaEncryptor;
private boolean enable;
private String headerFlag; private String headerFlag;
@Override @Override
public void init(FilterConfig filterConfig) throws ServletException { public void init(FilterConfig filterConfig) throws ServletException {
String enableStr = filterConfig.getInitParameter(CryptoFilter.CRYPTO_ENABLE);
enable = Boolean.parseBoolean(enableStr);
EncryptContext encryptContext = new EncryptContext(); EncryptContext encryptContext = new EncryptContext();
encryptContext.setAlgorithm(AlgorithmType.RSA); encryptContext.setAlgorithm(AlgorithmType.RSA);
encryptContext.setPublicKey(filterConfig.getInitParameter(CryptoFilter.CRYPTO_PUBLIC_KEY)); encryptContext.setPublicKey(filterConfig.getInitParameter(CryptoFilter.CRYPTO_PUBLIC_KEY));
@ -38,13 +35,14 @@ public class CryptoFilter implements Filter {
rsaEncryptor = new RsaEncryptor(encryptContext); rsaEncryptor = new RsaEncryptor(encryptContext);
} }
@SneakyThrows
@Override @Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
throws IOException, ServletException {
ServletRequest requestWrapper = null; ServletRequest requestWrapper = null;
if (enable && request instanceof HttpServletRequest HttpServletRequest httpServletRequest = (HttpServletRequest) request;
&& StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) { if (StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)
requestWrapper = new DecryptRequestBodyWrapper((HttpServletRequest) request, rsaEncryptor, headerFlag); && (HttpMethod.PUT.matches(httpServletRequest.getMethod()) || HttpMethod.POST.matches(httpServletRequest.getMethod()))) {
requestWrapper = new DecryptRequestBodyWrapper(httpServletRequest, rsaEncryptor, headerFlag);
} }
if (null == requestWrapper) { if (null == requestWrapper) {
chain.doFilter(request, response); chain.doFilter(request, response);

View File

@ -1,11 +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.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.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;
@ -14,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;
/** /**
@ -30,16 +33,23 @@ public class FilterConfig {
@Autowired @Autowired
private RequestEncryptProperties requestEncryptProperties; private RequestEncryptProperties requestEncryptProperties;
@Autowired
private DecryptUrlHandler decryptUrlHandler;
@Bean @Bean
@ConditionalOnProperty(value = "request-encryptor.enable", havingValue = "true")
public FilterRegistrationBean<CryptoFilter> cryptoFilterRegistration() { public FilterRegistrationBean<CryptoFilter> cryptoFilterRegistration() {
FilterRegistrationBean<CryptoFilter> registration = new FilterRegistrationBean<>(); FilterRegistrationBean<CryptoFilter> registration = new FilterRegistrationBean<>();
registration.setDispatcherTypes(DispatcherType.REQUEST); registration.setDispatcherTypes(DispatcherType.REQUEST);
registration.setFilter(new CryptoFilter()); registration.setFilter(new CryptoFilter());
registration.addUrlPatterns("/*"); 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"); registration.setName("cryptoFilter");
HashMap<String, String> param = new HashMap<>(); HashMap<String, String> param = new HashMap<>();
param.put(CryptoFilter.CRYPTO_ENABLE, String.valueOf(requestEncryptProperties.getEnable()));
param.put(CryptoFilter.CRYPTO_PUBLIC_KEY, requestEncryptProperties.getPublicKey()); param.put(CryptoFilter.CRYPTO_PUBLIC_KEY, requestEncryptProperties.getPublicKey());
param.put(CryptoFilter.CRYPTO_PRIVATE_KEY, requestEncryptProperties.getPrivateKey()); param.put(CryptoFilter.CRYPTO_PRIVATE_KEY, requestEncryptProperties.getPrivateKey());
param.put(CryptoFilter.CRYPTO_HEADER_FLAG, requestEncryptProperties.getHeaderFlag()); param.put(CryptoFilter.CRYPTO_HEADER_FLAG, requestEncryptProperties.getHeaderFlag());
@ -57,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;

View File

@ -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);
}
}

View File

@ -31,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 请根据实际情况自行修改
} }
@ -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 密钥 // 生成一个 AES 密钥
const aesKey = generateAesKey(); const aesKey = generateAesKey();
config.headers['AES'] = encrypt(aesKey.toString(CryptoJS.enc.Base64)); config.headers['AES'] = encrypt(aesKey.toString(CryptoJS.enc.Base64));

View File

@ -31,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 || 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 请根据实际情况自行修改
} }
@ -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 密钥 // 生成一个 AES 密钥
const aesKey = generateAesKey(); const aesKey = generateAesKey();
config.headers['AES'] = encrypt(aesKey.toString(CryptoJS.enc.Base64)); config.headers['AES'] = encrypt(aesKey.toString(CryptoJS.enc.Base64));