加解密配置
This commit is contained in:
parent
cf9bc4d8c2
commit
a5a948034a
|
|
@ -52,4 +52,31 @@ public class CacheConstants
|
|||
* 登录IP黑名单 cache key
|
||||
*/
|
||||
public static final String SYS_LOGIN_BLACKIPLIST ="blackIPList";
|
||||
|
||||
|
||||
/**
|
||||
* 系统配置
|
||||
* */
|
||||
public static final String SYSTEM_CONFIG_VOS ="systemConfigVos";
|
||||
|
||||
/**
|
||||
* 请求加密
|
||||
* */
|
||||
public static final String ENCRYPT_REQUEST ="encryptRequest";
|
||||
|
||||
/**
|
||||
* 响应加密
|
||||
* */
|
||||
public static final String ENCRYPT_RESPONSE ="encryptResponse";
|
||||
|
||||
/**
|
||||
* 数据完整性校验
|
||||
* */
|
||||
public static final String CHECK_INTEGRITY ="checkIntegrity";
|
||||
|
||||
/**
|
||||
* 防重放请求
|
||||
* */
|
||||
public static final String REPLAY_ATTACK ="replayAttack";
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
/*
|
||||
package com.bonus.common.filter;
|
||||
|
||||
import com.bonus.common.exception.CaptchaException;
|
||||
|
|
@ -20,29 +21,37 @@ import java.io.InputStreamReader;
|
|||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
*/
|
||||
/**
|
||||
* 请求内容存储 处理请求内容 内容放在gatewayContext中
|
||||
* 解决数据流被重复读取无数据的 问题
|
||||
* 对formData 数据进行解密
|
||||
*
|
||||
* @author bonus
|
||||
*/
|
||||
*//*
|
||||
|
||||
|
||||
@Configuration
|
||||
@Slf4j
|
||||
public class RequestCoverFilter implements Filter {
|
||||
|
||||
/**
|
||||
*/
|
||||
/**
|
||||
* 数据加密标志
|
||||
*/
|
||||
*//*
|
||||
|
||||
public static final String ENCRYPT = "encryptRequest";
|
||||
/**
|
||||
*/
|
||||
/**
|
||||
* 数据完整性校验标志
|
||||
*/
|
||||
*//*
|
||||
|
||||
public static final String INTEGRALITY = "checkIntegrity";
|
||||
/**
|
||||
*/
|
||||
/**
|
||||
* 完整性校验哈希值
|
||||
*/
|
||||
*//*
|
||||
|
||||
public static final String HMAC_HEADER_NAME = "Params-Hash";
|
||||
|
||||
@Override
|
||||
|
|
@ -79,9 +88,11 @@ public class RequestCoverFilter implements Filter {
|
|||
// 清理逻辑
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
/**
|
||||
* 处理 multipart/form-data 请求
|
||||
*/
|
||||
*//*
|
||||
|
||||
private void handleMultipartRequest(HttpServletRequest request, ServletResponse response,
|
||||
FilterChain chain, boolean integrality, boolean encrypt)
|
||||
throws IOException, ServletException {
|
||||
|
|
@ -95,9 +106,11 @@ public class RequestCoverFilter implements Filter {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
/**
|
||||
* 处理请求体里的参数
|
||||
*/
|
||||
*//*
|
||||
|
||||
private void handleBodyRequest(HttpServletRequest request, ServletResponse response,
|
||||
FilterChain chain, boolean integrality, boolean encrypt)
|
||||
throws IOException, ServletException {
|
||||
|
|
@ -152,9 +165,11 @@ public class RequestCoverFilter implements Filter {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
/**
|
||||
* 处理URL参数
|
||||
*/
|
||||
*//*
|
||||
|
||||
private void handleUrlParams(HttpServletRequest request, ServletResponse response,
|
||||
FilterChain chain, boolean integrality, boolean encrypt)
|
||||
throws IOException, ServletException {
|
||||
|
|
@ -168,9 +183,11 @@ public class RequestCoverFilter implements Filter {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
/**
|
||||
* 更新查询参数,解密和验证数据完整性
|
||||
*/
|
||||
*//*
|
||||
|
||||
private HttpServletRequest updateRequestParam(HttpServletRequest request,
|
||||
boolean integrality, boolean encrypt) {
|
||||
|
||||
|
|
@ -228,9 +245,11 @@ public class RequestCoverFilter implements Filter {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
/**
|
||||
* 数据完整性校验
|
||||
*/
|
||||
*//*
|
||||
|
||||
private void integrityVerification(String providedHmac, String query) {
|
||||
if (providedHmac == null) {
|
||||
log.error("完整性校验哈希值为空");
|
||||
|
|
@ -251,9 +270,11 @@ public class RequestCoverFilter implements Filter {
|
|||
System.err.println(calculatedHash);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
/**
|
||||
* 请求体包装类
|
||||
*/
|
||||
*//*
|
||||
|
||||
private static class BodyCachingRequestWrapper extends HttpServletRequestWrapper {
|
||||
private final String body;
|
||||
|
||||
|
|
@ -294,9 +315,11 @@ public class RequestCoverFilter implements Filter {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
/**
|
||||
* 查询字符串包装类
|
||||
*/
|
||||
*//*
|
||||
|
||||
private static class QueryStringRequestWrapper extends HttpServletRequestWrapper {
|
||||
private final String queryString;
|
||||
private Map<String, String[]> cachedParameterMap;
|
||||
|
|
@ -336,9 +359,11 @@ public class RequestCoverFilter implements Filter {
|
|||
return getParameterMap().get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
/**
|
||||
* 解析查询字符串,支持普通格式和嵌套格式
|
||||
*/
|
||||
*//*
|
||||
|
||||
private Map<String, String[]> parseQueryString(String queryString) {
|
||||
Map<String, String[]> parameterMap = new HashMap<>();
|
||||
if (queryString == null || queryString.trim().isEmpty()) {
|
||||
|
|
@ -384,9 +409,11 @@ public class RequestCoverFilter implements Filter {
|
|||
return parameterMap;
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
/**
|
||||
* 添加参数到Map,支持多值参数
|
||||
*/
|
||||
*//*
|
||||
|
||||
private void addParameter(Map<String, String[]> parameterMap, String key, String value) {
|
||||
if (parameterMap.containsKey(key)) {
|
||||
String[] existingValues = parameterMap.get(key);
|
||||
|
|
@ -399,9 +426,11 @@ public class RequestCoverFilter implements Filter {
|
|||
// log.info("添加参数: {} = {}", key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
/**
|
||||
* 简单解析查询字符串(备用方案)
|
||||
*/
|
||||
*//*
|
||||
|
||||
private Map<String, String[]> parseSimpleQueryString(String queryString) {
|
||||
Map<String, String[]> parameterMap = new HashMap<>();
|
||||
if (queryString != null) {
|
||||
|
|
@ -419,10 +448,12 @@ public class RequestCoverFilter implements Filter {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
/**
|
||||
* Multipart 请求包装类
|
||||
* 只处理 params 字段的解密(params 是 JSON 字符串),忽略文件字段
|
||||
*/
|
||||
*//*
|
||||
|
||||
private static class MultipartRequestWrapper extends HttpServletRequestWrapper {
|
||||
private final Map<String, String[]> parameterMap;
|
||||
private final boolean integrality;
|
||||
|
|
@ -440,9 +471,11 @@ public class RequestCoverFilter implements Filter {
|
|||
processParams();
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
/**
|
||||
* 处理 params 参数(JSON 字符串格式)
|
||||
*/
|
||||
*//*
|
||||
|
||||
private void processParams() {
|
||||
String[] paramsValues = parameterMap.get("params");
|
||||
if (paramsValues != null && paramsValues.length > 0) {
|
||||
|
|
@ -482,9 +515,11 @@ public class RequestCoverFilter implements Filter {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
/**
|
||||
* 数据完整性校验
|
||||
*/
|
||||
*//*
|
||||
|
||||
private void integrityVerification(String providedHmac, String data) {
|
||||
if (providedHmac == null) {
|
||||
log.error("完整性校验哈希值为空");
|
||||
|
|
@ -531,4 +566,4 @@ public class RequestCoverFilter implements Filter {
|
|||
return ((HttpServletRequest) getRequest()).getPart(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
/*
|
||||
package com.bonus.common.filter;
|
||||
|
||||
import com.bonus.common.utils.encryption.Sm4Utils;
|
||||
|
|
@ -10,18 +11,22 @@ import javax.servlet.http.HttpServletResponse;
|
|||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
*/
|
||||
/**
|
||||
* 对返回的 data数据进行加密
|
||||
*
|
||||
* @author 黑子
|
||||
*/
|
||||
*//*
|
||||
|
||||
@Configuration
|
||||
@Slf4j
|
||||
public class ResponseEncryptFilter implements Filter {
|
||||
|
||||
/**
|
||||
*/
|
||||
/**
|
||||
* 加密标识
|
||||
*/
|
||||
*//*
|
||||
|
||||
private static final String ENCRYPT_RESPONSE = "encryptResponse";
|
||||
|
||||
@Override
|
||||
|
|
@ -87,15 +92,18 @@ public class ResponseEncryptFilter implements Filter {
|
|||
// 清理逻辑
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
/**
|
||||
* 判断是否需要对响应数据进行加密
|
||||
*
|
||||
* @param request 请求对象
|
||||
* @return 如果需要加密返回true,否则返回false
|
||||
*/
|
||||
*//*
|
||||
|
||||
private boolean shouldEncrypt(HttpServletRequest request) {
|
||||
String encryptHeader = request.getHeader(ENCRYPT_RESPONSE);
|
||||
return "true".equalsIgnoreCase(encryptHeader);
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ package com.bonus.framework.config;
|
|||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.DispatcherType;
|
||||
|
||||
import com.bonus.common.filter.RequestCoverFilter;
|
||||
import com.bonus.common.filter.ResponseEncryptFilter;
|
||||
import com.bonus.framework.filter.IpWhitelistFilter;
|
||||
import com.bonus.framework.filter.ReplayAttackFilter;
|
||||
import com.bonus.framework.filter.RequestCoverFilter;
|
||||
import com.bonus.framework.filter.ResponseEncryptFilter;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
|
|
@ -32,6 +32,12 @@ public class FilterConfig
|
|||
@Value("${xss.urlPatterns}")
|
||||
private String urlPatterns;
|
||||
|
||||
@Resource(name = "RequestCoverFilter")
|
||||
private RequestCoverFilter requestCoverFilter;
|
||||
|
||||
@Resource(name = "ResponseEncryptFilter")
|
||||
private ResponseEncryptFilter responseEncryptFilter;
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "xss.enabled", havingValue = "true")
|
||||
|
|
@ -64,7 +70,7 @@ public class FilterConfig
|
|||
@Bean
|
||||
public FilterRegistrationBean<RequestCoverFilter> requestCoverFilterRegistration() {
|
||||
FilterRegistrationBean<RequestCoverFilter> registration = new FilterRegistrationBean<>();
|
||||
registration.setFilter(new RequestCoverFilter());
|
||||
registration.setFilter(requestCoverFilter);
|
||||
registration.addUrlPatterns("/*");
|
||||
// registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
|
||||
registration.setOrder(2);
|
||||
|
|
@ -78,7 +84,7 @@ public class FilterConfig
|
|||
@Bean
|
||||
public FilterRegistrationBean<ResponseEncryptFilter> responseEncryptFilterRegistration() {
|
||||
FilterRegistrationBean<ResponseEncryptFilter> registration = new FilterRegistrationBean<>();
|
||||
registration.setFilter(new ResponseEncryptFilter());
|
||||
registration.setFilter(responseEncryptFilter);
|
||||
registration.addUrlPatterns("/*");
|
||||
registration.setOrder(-5); // 设置过滤器顺序
|
||||
registration.setName("responseEncryptFilter");
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ public class SecurityConfig
|
|||
.authorizeHttpRequests((requests) -> {
|
||||
permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll());
|
||||
// 对于登录login 注册register 验证码captchaImage 允许匿名访问
|
||||
requests.antMatchers("/login", "/register", "/captchaImage","/session/check").permitAll()
|
||||
requests.antMatchers("/login", "/register", "/captchaImage","/sys/config/getConfig","/session/check").permitAll()
|
||||
// 静态资源,可匿名访问
|
||||
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
|
||||
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
package com.bonus.framework.filter;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.bonus.common.constant.CacheConstants;
|
||||
import com.bonus.common.core.redis.RedisCache;
|
||||
import com.bonus.common.utils.IpWhitelistUtils;
|
||||
import com.bonus.system.domain.vo.SystemConfigVo;
|
||||
import com.bonus.system.service.ISysIpWhitelistService;
|
||||
import com.bonus.system.service.ISystemConfigService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
|
@ -14,6 +19,7 @@ import javax.servlet.http.HttpServletRequest;
|
|||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
|
@ -24,12 +30,24 @@ public class IpWhitelistFilter implements Filter {
|
|||
@Autowired
|
||||
private ISysIpWhitelistService whitelistService;
|
||||
|
||||
@Autowired
|
||||
private ISystemConfigService configService;
|
||||
|
||||
@Autowired
|
||||
private RedisCache redisUtil;
|
||||
|
||||
|
||||
|
||||
private final AntPathMatcher pathMatcher = new AntPathMatcher();
|
||||
private final ConcurrentHashMap<String, CacheEntry> ipCache = new ConcurrentHashMap<>();
|
||||
private static final long CACHE_REFRESH_INTERVAL = TimeUnit.MINUTES.toMillis(1);
|
||||
private static final long ENTRY_TTL = TimeUnit.MINUTES.toMillis(1);
|
||||
private static final long refreshTime = 1000 * 60;
|
||||
private static final long refreshTime = 1000 * 60 * 5;
|
||||
private static final long refreshTime2 = 1000 * 60 * 29;
|
||||
|
||||
private static final long TIMESTAMP_TOLERANCE = 15 * 60 * 1000; // 15分钟
|
||||
// 请求签名在Redis中的过期时间(秒)
|
||||
private static final int SIGNATURE_EXPIRE_SECONDS = (int) (TIMESTAMP_TOLERANCE * 2 / 1000);
|
||||
private static final String[] EXCLUDE_PATHS = {
|
||||
// 排除路径
|
||||
};
|
||||
|
|
@ -113,6 +131,16 @@ public class IpWhitelistFilter implements Filter {
|
|||
// log.info("IP白名单缓存刷新完成,清理了 {} 个缓存条目", sizeBefore);
|
||||
}
|
||||
|
||||
@Scheduled(fixedRate = refreshTime2)
|
||||
public void refreshSystemConfigCache() {
|
||||
log.info("开始定时刷新系统配置缓存...");
|
||||
List<SystemConfigVo> systemConfigVos = configService.listConfig();
|
||||
Boolean stored = redisUtil.setNxCacheObject(CacheConstants.SYSTEM_CONFIG_VOS,
|
||||
JSON.toJSONString(systemConfigVos),
|
||||
(long) SIGNATURE_EXPIRE_SECONDS,
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void sendForbiddenResponse(HttpServletResponse response, String message) throws IOException {
|
||||
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.bonus.framework.filter;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
|
@ -8,15 +9,22 @@ import java.nio.charset.StandardCharsets;
|
|||
import java.security.MessageDigest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.bonus.common.constant.CacheConstants;
|
||||
import com.bonus.common.core.domain.entity.SysUser;
|
||||
import com.bonus.common.core.domain.model.LoginUser;
|
||||
import com.bonus.common.core.redis.RedisCache;
|
||||
import com.bonus.common.utils.encryption.Sm4Utils;
|
||||
import com.bonus.framework.web.service.TokenService;
|
||||
import com.bonus.system.domain.vo.SystemConfigVo;
|
||||
import com.bonus.system.service.ISystemConfigService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
|
@ -28,8 +36,9 @@ import org.springframework.stereotype.Component;
|
|||
* @version:1.0
|
||||
* @description:防重放攻击过滤器
|
||||
*/
|
||||
@Configuration
|
||||
//@Configuration
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ReplayAttackFilter implements Filter {
|
||||
static List<String> ignoreUrlPatterns = new ArrayList<>();
|
||||
static {
|
||||
|
|
@ -39,13 +48,20 @@ public class ReplayAttackFilter implements Filter {
|
|||
ignoreUrlPatterns.add("/smartArchives/getInfo");
|
||||
ignoreUrlPatterns.add("/smartArchives/getRouters");
|
||||
ignoreUrlPatterns.add("/smartArchives/session/check");
|
||||
ignoreUrlPatterns.add("/smartArchives/sys/config/getConfig");
|
||||
}
|
||||
|
||||
private final RedisCache redisUtil;
|
||||
|
||||
public ReplayAttackFilter(RedisCache redisUtil) {
|
||||
this.redisUtil = redisUtil;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private RedisCache redisUtil;
|
||||
private TokenService tokenService;
|
||||
|
||||
@Autowired
|
||||
private TokenService tokenService;
|
||||
private ISystemConfigService configService;
|
||||
|
||||
// 时间戳允许的误差范围(毫秒)
|
||||
private static final long TIMESTAMP_TOLERANCE = 5 * 60 * 1000; // 5分钟
|
||||
|
|
@ -54,12 +70,56 @@ public class ReplayAttackFilter implements Filter {
|
|||
// Redis键前缀
|
||||
private static final String SIGNATURE_KEY_PREFIX = "replay:signature:";
|
||||
|
||||
|
||||
private static final long TIMESTAMP_TOLERANCE2 = 15 * 60 * 1000; // 15分钟
|
||||
// 请求签名在Redis中的过期时间(秒)
|
||||
private static final int SIGNATURE_EXPIRE_SECONDS2 = (int) (TIMESTAMP_TOLERANCE2 * 2 / 1000);
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
|
||||
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||
HttpServletResponse httpResponse = (HttpServletResponse) response;
|
||||
|
||||
Object cacheObject = redisUtil.getCacheObject(CacheConstants.SYSTEM_CONFIG_VOS);
|
||||
if(Objects.isNull(cacheObject)){
|
||||
List<SystemConfigVo> systemConfigVos = configService.listConfig();
|
||||
Boolean stored = redisUtil.setNxCacheObject(CacheConstants.SYSTEM_CONFIG_VOS,
|
||||
JSON.toJSONString(systemConfigVos),
|
||||
(long) SIGNATURE_EXPIRE_SECONDS,
|
||||
TimeUnit.SECONDS);
|
||||
if(CollectionUtils.isNotEmpty(systemConfigVos)){
|
||||
SystemConfigVo config = systemConfigVos.stream()
|
||||
.filter(item -> CacheConstants.REPLAY_ATTACK.equals(item.getConfigCode()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if(Objects.nonNull(config)){
|
||||
String useStatus = config.getUseStatus();
|
||||
if(Objects.equals("1",useStatus)){
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
String cacheStr = (String) cacheObject;
|
||||
List<SystemConfigVo> systemConfigVos = JSON.parseArray(cacheStr, SystemConfigVo.class);
|
||||
if(CollectionUtils.isNotEmpty(systemConfigVos)){
|
||||
SystemConfigVo config = systemConfigVos.stream()
|
||||
.filter(item -> CacheConstants.REPLAY_ATTACK.equals(item.getConfigCode()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if(Objects.nonNull(config)){
|
||||
String useStatus = config.getUseStatus();
|
||||
if(Objects.equals("1",useStatus)){
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 跳过OPTIONS预检请求
|
||||
if ("OPTIONS".equalsIgnoreCase(httpRequest.getMethod())) {
|
||||
chain.doFilter(request, response);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,609 @@
|
|||
package com.bonus.framework.filter;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.bonus.common.constant.CacheConstants;
|
||||
import com.bonus.common.core.redis.RedisCache;
|
||||
import com.bonus.common.exception.CaptchaException;
|
||||
import com.bonus.common.utils.encryption.Sm3Util;
|
||||
import com.bonus.common.utils.encryption.Sm4Utils;
|
||||
import com.bonus.system.domain.vo.SystemConfigVo;
|
||||
import com.bonus.system.service.ISystemConfigService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
import javax.servlet.http.Part;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 请求内容存储 处理请求内容 内容放在gatewayContext中
|
||||
* 解决数据流被重复读取无数据的 问题
|
||||
* 对formData 数据进行解密
|
||||
*
|
||||
* @author bonus
|
||||
*/
|
||||
|
||||
//@Configuration
|
||||
@Slf4j
|
||||
@Component(value = "RequestCoverFilter")
|
||||
public class RequestCoverFilter implements Filter {
|
||||
|
||||
/**
|
||||
* 数据加密标志
|
||||
*/
|
||||
public static final String ENCRYPT = "encryptRequest";
|
||||
/**
|
||||
* 数据完整性校验标志
|
||||
*/
|
||||
public static final String INTEGRALITY = "checkIntegrity";
|
||||
/**
|
||||
* 完整性校验哈希值
|
||||
*/
|
||||
public static final String HMAC_HEADER_NAME = "Params-Hash";
|
||||
|
||||
private final RedisCache redisUtil;
|
||||
|
||||
public RequestCoverFilter(RedisCache redisUtil) {
|
||||
this.redisUtil = redisUtil;
|
||||
}
|
||||
|
||||
|
||||
@Autowired
|
||||
private ISystemConfigService configService;
|
||||
|
||||
private static final long TIMESTAMP_TOLERANCE = 15 * 60 * 1000; // 15分钟
|
||||
// 请求签名在Redis中的过期时间(秒)
|
||||
private static final int SIGNATURE_EXPIRE_SECONDS = (int) (TIMESTAMP_TOLERANCE * 2 / 1000);
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
// 初始化逻辑
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
|
||||
|
||||
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||
// 获取系统请求加密和完整性校验状态
|
||||
boolean encrypt_request = getSystemConfigStatus(CacheConstants.ENCRYPT_REQUEST);
|
||||
boolean check_integrity = getSystemConfigStatus(CacheConstants.CHECK_INTEGRITY);
|
||||
// 获取请求头信息
|
||||
boolean integrality = "true".equalsIgnoreCase(httpRequest.getHeader(INTEGRALITY)) && check_integrity;
|
||||
boolean encrypt = "true".equalsIgnoreCase(httpRequest.getHeader(ENCRYPT)) && encrypt_request;
|
||||
|
||||
String contentType = httpRequest.getContentType();
|
||||
|
||||
// 处理不同类型的请求
|
||||
if (contentType == null) {
|
||||
// log.info("请求头中无Content-Type信息,处理URL参数。");
|
||||
handleUrlParams(httpRequest, response, chain, integrality, encrypt);
|
||||
} else if (contentType.contains(MediaType.APPLICATION_JSON_VALUE)) {
|
||||
handleBodyRequest(httpRequest, response, chain, integrality, encrypt);
|
||||
} else if (contentType.contains(MediaType.MULTIPART_FORM_DATA_VALUE)) {
|
||||
handleMultipartRequest(httpRequest, response, chain, integrality, encrypt);
|
||||
} else {
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
// 清理逻辑
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 multipart/form-data 请求
|
||||
*/
|
||||
private void handleMultipartRequest(HttpServletRequest request, ServletResponse response,
|
||||
FilterChain chain, boolean integrality, boolean encrypt)
|
||||
throws IOException, ServletException {
|
||||
try {
|
||||
// 创建包装的请求对象,处理 multipart 请求
|
||||
MultipartRequestWrapper wrappedRequest = new MultipartRequestWrapper(request, integrality, encrypt);
|
||||
chain.doFilter(wrappedRequest, response);
|
||||
} catch (Exception e) {
|
||||
log.error("处理 multipart 请求时发生错误: {}", e.getMessage(), e);
|
||||
throw new ServletException("请求处理失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理请求体里的参数
|
||||
*/
|
||||
private void handleBodyRequest(HttpServletRequest request, ServletResponse response,
|
||||
FilterChain chain, boolean integrality, boolean encrypt)
|
||||
throws IOException, ServletException {
|
||||
|
||||
// 读取请求体内容
|
||||
String requestBody = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8);
|
||||
|
||||
if (ObjectUtils.isEmpty(requestBody)) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
// 去掉多余的引号(如果有)
|
||||
if (requestBody.startsWith("\"") && requestBody.endsWith("\"")) {
|
||||
requestBody = requestBody.substring(1, requestBody.length() - 1);
|
||||
}
|
||||
|
||||
try {
|
||||
// 解密请求体
|
||||
if (encrypt) {
|
||||
requestBody = Sm4Utils.decrypt(requestBody);
|
||||
}
|
||||
|
||||
if (ObjectUtils.isEmpty(requestBody)) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
// 校验数据完整性
|
||||
if (integrality) {
|
||||
String[] parts = requestBody.split("\\|");
|
||||
if (parts.length != 2) {
|
||||
log.error("解密后的请求体格式不正确: {}", requestBody);
|
||||
throw new CaptchaException("请求参数不正确");
|
||||
}
|
||||
integrityVerification(parts[1], parts[0]);
|
||||
requestBody = parts[0];
|
||||
}
|
||||
|
||||
if (ObjectUtils.isEmpty(requestBody)) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建包装的请求对象
|
||||
HttpServletRequest wrappedRequest = new BodyCachingRequestWrapper(request, requestBody);
|
||||
chain.doFilter(wrappedRequest, response);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("处理请求体时发生错误: {}", e.getMessage(), e);
|
||||
throw new ServletException("请求处理失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理URL参数
|
||||
*/
|
||||
private void handleUrlParams(HttpServletRequest request, ServletResponse response,
|
||||
FilterChain chain, boolean integrality, boolean encrypt)
|
||||
throws IOException, ServletException {
|
||||
|
||||
try {
|
||||
HttpServletRequest updatedRequest = updateRequestParam(request, integrality, encrypt);
|
||||
chain.doFilter(updatedRequest != null ? updatedRequest : request, response);
|
||||
} catch (Exception e) {
|
||||
log.error("处理 GET 请求时发生错误: {}", e.getMessage(), e);
|
||||
throw new ServletException("请求处理失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新查询参数,解密和验证数据完整性
|
||||
*/
|
||||
private HttpServletRequest updateRequestParam(HttpServletRequest request,
|
||||
boolean integrality, boolean encrypt) {
|
||||
|
||||
String queryString = request.getQueryString();
|
||||
if (!ObjectUtils.isEmpty(queryString)) {
|
||||
try {
|
||||
// 解析 params 参数
|
||||
String encryptedParams = null;
|
||||
String[] queryParams = queryString.split("&");
|
||||
for (String param : queryParams) {
|
||||
if (param.startsWith("params=")) {
|
||||
encryptedParams = param.substring(7); // 去掉 "params="
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (encryptedParams == null) {
|
||||
// log.warn("未找到加密参数,跳过解密处理");
|
||||
return null;
|
||||
}
|
||||
|
||||
// URL解码
|
||||
encryptedParams = java.net.URLDecoder.decode(encryptedParams, StandardCharsets.UTF_8.name());
|
||||
|
||||
String query = encryptedParams;
|
||||
|
||||
// 解密查询参数
|
||||
if (encrypt) {
|
||||
query = Sm4Utils.decrypt(query);
|
||||
}
|
||||
|
||||
// 校验数据完整性
|
||||
if (integrality) {
|
||||
String[] parts = query.split("\\|");
|
||||
if (parts.length != 2) {
|
||||
log.error("解密后的参数格式不正确: {}", query);
|
||||
throw new CaptchaException("请求参数不正确");
|
||||
}
|
||||
integrityVerification(parts[1], parts[0]);
|
||||
query = parts[0]; // 只保留原始参数部分
|
||||
}
|
||||
|
||||
if (ObjectUtils.isEmpty(query)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 创建新的请求对象
|
||||
return new QueryStringRequestWrapper(request, query);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("解密查询参数时发生错误: {}", e.getMessage(), e);
|
||||
throw new CaptchaException("请求参数不正确");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据完整性校验
|
||||
*/
|
||||
private void integrityVerification(String providedHmac, String query) {
|
||||
if (providedHmac == null) {
|
||||
log.error("完整性校验哈希值为空");
|
||||
throw new CaptchaException("请求参数不正确");
|
||||
}
|
||||
String calculatedHash = Sm3Util.encrypt(query);
|
||||
// log.info("计算出的哈希值: {}", calculatedHash);
|
||||
// log.info("提供的哈希值: {}", providedHmac);
|
||||
if (!calculatedHash.equals(providedHmac)) {
|
||||
log.error("参数完整性校验失败");
|
||||
throw new CaptchaException("请求参数不正确");
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
String query = "pageNum=1&pageSize=10";
|
||||
String calculatedHash = Sm3Util.encrypt(query);
|
||||
System.err.println(calculatedHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求体包装类
|
||||
*/
|
||||
private static class BodyCachingRequestWrapper extends HttpServletRequestWrapper {
|
||||
private final String body;
|
||||
|
||||
public BodyCachingRequestWrapper(HttpServletRequest request, String body) {
|
||||
super(request);
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletInputStream getInputStream() throws IOException {
|
||||
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8));
|
||||
return new ServletInputStream() {
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return byteArrayInputStream.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFinished() {
|
||||
return byteArrayInputStream.available() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReady() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadListener(ReadListener listener) {
|
||||
// 不需要实现
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedReader getReader() throws IOException {
|
||||
return new BufferedReader(new InputStreamReader(getInputStream()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询字符串包装类
|
||||
*/
|
||||
private static class QueryStringRequestWrapper extends HttpServletRequestWrapper {
|
||||
private final String queryString;
|
||||
private Map<String, String[]> cachedParameterMap;
|
||||
|
||||
public QueryStringRequestWrapper(HttpServletRequest request, String queryString) {
|
||||
super(request);
|
||||
this.queryString = queryString;
|
||||
// log.info("QueryStringRequestWrapper 接收到的参数: {}", queryString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQueryString() {
|
||||
return queryString;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParameter(String name) {
|
||||
String[] values = getParameterMap().get(name);
|
||||
return (values != null && values.length > 0) ? values[0] : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String[]> getParameterMap() {
|
||||
if (cachedParameterMap == null) {
|
||||
cachedParameterMap = parseQueryString(queryString);
|
||||
}
|
||||
return cachedParameterMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> getParameterNames() {
|
||||
return Collections.enumeration(getParameterMap().keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getParameterValues(String name) {
|
||||
return getParameterMap().get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析查询字符串,支持普通格式和嵌套格式
|
||||
*/
|
||||
private Map<String, String[]> parseQueryString(String queryString) {
|
||||
Map<String, String[]> parameterMap = new HashMap<>();
|
||||
if (queryString == null || queryString.trim().isEmpty()) {
|
||||
return parameterMap;
|
||||
}
|
||||
|
||||
// log.info("开始解析查询字符串: {}", queryString);
|
||||
|
||||
try {
|
||||
// 先URL解码
|
||||
String decodedQueryString = java.net.URLDecoder.decode(queryString, StandardCharsets.UTF_8.name());
|
||||
String[] pairs = decodedQueryString.split("&");
|
||||
|
||||
for (String pair : pairs) {
|
||||
if (pair == null || pair.trim().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String[] keyValue = pair.split("=", 2);
|
||||
if (keyValue.length >= 1) {
|
||||
String key = keyValue[0].trim();
|
||||
String value = keyValue.length == 2 ? keyValue[1].trim() : "";
|
||||
|
||||
// 处理嵌套参数格式(如 params[beginTime])
|
||||
if (key.startsWith("params[") && key.endsWith("]")) {
|
||||
String nestedKey = key.substring(7, key.length() - 1);
|
||||
String paramsKey = "params[" + nestedKey + "]";
|
||||
addParameter(parameterMap, paramsKey, value);
|
||||
} else {
|
||||
// 处理普通参数(如 pageNum=1, pageSize=10)
|
||||
addParameter(parameterMap, key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warn("解析查询字符串失败: {}", e.getMessage());
|
||||
// 失败时尝试简单解析
|
||||
return parseSimpleQueryString(queryString);
|
||||
}
|
||||
|
||||
// log.info("解析后的参数Map: {}", parameterMap);
|
||||
return parameterMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加参数到Map,支持多值参数
|
||||
*/
|
||||
private void addParameter(Map<String, String[]> parameterMap, String key, String value) {
|
||||
if (parameterMap.containsKey(key)) {
|
||||
String[] existingValues = parameterMap.get(key);
|
||||
String[] newValues = Arrays.copyOf(existingValues, existingValues.length + 1);
|
||||
newValues[existingValues.length] = value;
|
||||
parameterMap.put(key, newValues);
|
||||
} else {
|
||||
parameterMap.put(key, new String[]{value});
|
||||
}
|
||||
// log.info("添加参数: {} = {}", key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 简单解析查询字符串(备用方案)
|
||||
*/
|
||||
private Map<String, String[]> parseSimpleQueryString(String queryString) {
|
||||
Map<String, String[]> parameterMap = new HashMap<>();
|
||||
if (queryString != null) {
|
||||
String[] pairs = queryString.split("&");
|
||||
for (String pair : pairs) {
|
||||
String[] keyValue = pair.split("=", 2);
|
||||
if (keyValue.length == 2) {
|
||||
String key = keyValue[0];
|
||||
String value = keyValue[1];
|
||||
addParameter(parameterMap, key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return parameterMap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Multipart 请求包装类
|
||||
* 只处理 params 字段的解密(params 是 JSON 字符串),忽略文件字段
|
||||
*/
|
||||
private static class MultipartRequestWrapper extends HttpServletRequestWrapper {
|
||||
private final Map<String, String[]> parameterMap;
|
||||
private final boolean integrality;
|
||||
private final boolean encrypt;
|
||||
|
||||
public MultipartRequestWrapper(HttpServletRequest request, boolean integrality, boolean encrypt) {
|
||||
super(request);
|
||||
this.integrality = integrality;
|
||||
this.encrypt = encrypt;
|
||||
|
||||
// 复制原始参数映射
|
||||
this.parameterMap = new HashMap<>(request.getParameterMap());
|
||||
|
||||
// 处理 params 参数
|
||||
processParams();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 params 参数(JSON 字符串格式)
|
||||
*/
|
||||
private void processParams() {
|
||||
String[] paramsValues = parameterMap.get("params");
|
||||
if (paramsValues != null && paramsValues.length > 0) {
|
||||
String encryptedParams = paramsValues[0];
|
||||
try {
|
||||
String decryptedParams = encryptedParams;
|
||||
|
||||
// 解密参数
|
||||
if (encrypt) {
|
||||
decryptedParams = Sm4Utils.decrypt(decryptedParams);
|
||||
}
|
||||
|
||||
// 校验数据完整性
|
||||
if (integrality) {
|
||||
String[] parts = decryptedParams.split("\\|");
|
||||
if (parts.length != 2) {
|
||||
log.error("解密后的参数格式不正确: {}", decryptedParams);
|
||||
throw new CaptchaException("请求参数不正确");
|
||||
}
|
||||
integrityVerification(parts[1], parts[0]);
|
||||
decryptedParams = parts[0]; // 只保留原始 JSON 字符串部分
|
||||
}
|
||||
|
||||
if (!ObjectUtils.isEmpty(decryptedParams)) {
|
||||
// 由于 params 是 JSON 字符串,我们将其作为原始参数保留
|
||||
// 而不是尝试解析为键值对,以保持 JSON 结构完整
|
||||
parameterMap.put("params", new String[]{decryptedParams});
|
||||
} else {
|
||||
// 移除空的 params 参数
|
||||
parameterMap.remove("params");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("处理 multipart params 参数时发生错误: {}", e.getMessage(), e);
|
||||
throw new CaptchaException("请求参数不正确");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据完整性校验
|
||||
*/
|
||||
private void integrityVerification(String providedHmac, String data) {
|
||||
if (providedHmac == null) {
|
||||
log.error("完整性校验哈希值为空");
|
||||
throw new CaptchaException("请求参数不正确");
|
||||
}
|
||||
String calculatedHash = Sm3Util.encrypt(data);
|
||||
log.info("计算出的哈希值: {}", calculatedHash);
|
||||
log.info("提供的哈希值: {}", providedHmac);
|
||||
if (!calculatedHash.equals(providedHmac)) {
|
||||
log.error("参数完整性校验失败");
|
||||
throw new CaptchaException("请求参数不正确");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParameter(String name) {
|
||||
String[] values = parameterMap.get(name);
|
||||
return (values != null && values.length > 0) ? values[0] : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String[]> getParameterMap() {
|
||||
return parameterMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> getParameterNames() {
|
||||
return Collections.enumeration(parameterMap.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getParameterValues(String name) {
|
||||
return parameterMap.get(name);
|
||||
}
|
||||
|
||||
// 重写文件相关方法,确保文件上传功能正常工作
|
||||
@Override
|
||||
public Collection<Part> getParts() throws IOException, ServletException {
|
||||
return ((HttpServletRequest) getRequest()).getParts();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Part getPart(String name) throws IOException, ServletException {
|
||||
return ((HttpServletRequest) getRequest()).getPart(name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统配置
|
||||
* @return boolean
|
||||
* @author cwchen
|
||||
* @date 2025/9/28 10:36
|
||||
*/
|
||||
public boolean getSystemConfigStatus(String key) {
|
||||
boolean SystemConfigStatus = false;
|
||||
Object cacheObject = redisUtil.getCacheObject(CacheConstants.SYSTEM_CONFIG_VOS);
|
||||
if(Objects.isNull(cacheObject)){
|
||||
List<SystemConfigVo> systemConfigVos = configService.listConfig();
|
||||
Boolean stored = redisUtil.setNxCacheObject(CacheConstants.SYSTEM_CONFIG_VOS,
|
||||
JSON.toJSONString(systemConfigVos),
|
||||
(long) SIGNATURE_EXPIRE_SECONDS,
|
||||
TimeUnit.SECONDS);
|
||||
if(CollectionUtils.isNotEmpty(systemConfigVos)){
|
||||
SystemConfigVo config = systemConfigVos.stream()
|
||||
.filter(item -> key.equals(item.getConfigCode()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if(Objects.nonNull(config)){
|
||||
String useStatus = config.getUseStatus();
|
||||
if(Objects.equals("0",useStatus)){
|
||||
SystemConfigStatus = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
String cacheStr = (String) cacheObject;
|
||||
List<SystemConfigVo> systemConfigVos = JSON.parseArray(cacheStr, SystemConfigVo.class);
|
||||
if(CollectionUtils.isNotEmpty(systemConfigVos)){
|
||||
SystemConfigVo config = systemConfigVos.stream()
|
||||
.filter(item -> key.equals(item.getConfigCode()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if(Objects.nonNull(config)){
|
||||
String useStatus = config.getUseStatus();
|
||||
if(Objects.equals("0",useStatus)){
|
||||
SystemConfigStatus = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return SystemConfigStatus;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
package com.bonus.framework.filter;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.bonus.common.constant.CacheConstants;
|
||||
import com.bonus.common.core.redis.RedisCache;
|
||||
import com.bonus.common.utils.encryption.Sm4Utils;
|
||||
import com.bonus.system.domain.vo.SystemConfigVo;
|
||||
import com.bonus.system.service.ISystemConfigService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.util.ContentCachingResponseWrapper;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.*;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 对返回的 data数据进行加密
|
||||
*
|
||||
* @author 黑子
|
||||
*/
|
||||
//@Configuration
|
||||
@Slf4j
|
||||
@Component(value = "ResponseEncryptFilter")
|
||||
public class ResponseEncryptFilter implements Filter {
|
||||
|
||||
/**
|
||||
* 加密标识
|
||||
*/
|
||||
private static final String ENCRYPT_RESPONSE = "encryptResponse";
|
||||
|
||||
private final RedisCache redisUtil;
|
||||
|
||||
public ResponseEncryptFilter(RedisCache redisUtil) {
|
||||
this.redisUtil = redisUtil;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private ISystemConfigService configService;
|
||||
|
||||
private static final long TIMESTAMP_TOLERANCE = 15 * 60 * 1000; // 15分钟
|
||||
// 请求签名在Redis中的过期时间(秒)
|
||||
private static final int SIGNATURE_EXPIRE_SECONDS = (int) (TIMESTAMP_TOLERANCE * 2 / 1000);
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
// 初始化逻辑
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
|
||||
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||
HttpServletResponse httpResponse = (HttpServletResponse) response;
|
||||
|
||||
// 是否需要相应加密
|
||||
boolean isResponseEncrypt = getSystemConfigStatus(CacheConstants.ENCRYPT_RESPONSE);
|
||||
// 检查请求头中是否包含加密标志
|
||||
if (shouldEncrypt(httpRequest) && isResponseEncrypt) {
|
||||
// 使用响应包装器来缓存响应内容
|
||||
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(httpResponse);
|
||||
|
||||
try {
|
||||
chain.doFilter(request, responseWrapper);
|
||||
|
||||
// 获取响应状态码
|
||||
int status = responseWrapper.getStatus();
|
||||
|
||||
// 只处理状态码为200 OK的响应
|
||||
if (status == HttpServletResponse.SC_OK) {
|
||||
// 获取响应内容
|
||||
byte[] responseBody = responseWrapper.getContentAsByteArray();
|
||||
|
||||
if (responseBody != null && responseBody.length > 0) {
|
||||
String responseData = new String(responseBody, StandardCharsets.UTF_8);
|
||||
|
||||
// 加密响应数据
|
||||
String encryptedData = Sm4Utils.encrypt(responseData);
|
||||
byte[] encryptedContent = encryptedData.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
// 设置加密后的响应
|
||||
responseWrapper.resetBuffer();
|
||||
responseWrapper.setContentLength(encryptedContent.length);
|
||||
responseWrapper.getOutputStream().write(encryptedContent);
|
||||
|
||||
// 添加响应头标识
|
||||
responseWrapper.setHeader(ENCRYPT_RESPONSE, "true");
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("响应加密处理时发生错误: {}", e.getMessage(), e);
|
||||
throw new ServletException("响应处理失败", e);
|
||||
} finally {
|
||||
// 确保响应被提交
|
||||
responseWrapper.copyBodyToResponse();
|
||||
}
|
||||
} else {
|
||||
// 不需要加密,直接继续处理链
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
// 清理逻辑
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否需要对响应数据进行加密
|
||||
*
|
||||
* @param request 请求对象
|
||||
* @return 如果需要加密返回true,否则返回false
|
||||
*/
|
||||
private boolean shouldEncrypt(HttpServletRequest request) {
|
||||
String encryptHeader = request.getHeader(ENCRYPT_RESPONSE);
|
||||
return "true".equalsIgnoreCase(encryptHeader);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统配置
|
||||
* @return boolean
|
||||
* @author cwchen
|
||||
* @date 2025/9/28 10:36
|
||||
*/
|
||||
public boolean getSystemConfigStatus(String key) {
|
||||
boolean isResponseEncrypt = false;
|
||||
Object cacheObject = redisUtil.getCacheObject(CacheConstants.SYSTEM_CONFIG_VOS);
|
||||
if (Objects.isNull(cacheObject)) {
|
||||
List<SystemConfigVo> systemConfigVos = configService.listConfig();
|
||||
Boolean stored = redisUtil.setNxCacheObject(CacheConstants.SYSTEM_CONFIG_VOS,
|
||||
JSON.toJSONString(systemConfigVos),
|
||||
(long) SIGNATURE_EXPIRE_SECONDS,
|
||||
TimeUnit.SECONDS);
|
||||
if (CollectionUtils.isNotEmpty(systemConfigVos)) {
|
||||
SystemConfigVo config = systemConfigVos.stream()
|
||||
.filter(item -> key.equals(item.getConfigCode()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (Objects.nonNull(config)) {
|
||||
String useStatus = config.getUseStatus();
|
||||
if (Objects.equals("0", useStatus)) {
|
||||
isResponseEncrypt = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
String cacheStr = (String) cacheObject;
|
||||
List<SystemConfigVo> systemConfigVos = JSON.parseArray(cacheStr, SystemConfigVo.class);
|
||||
if (CollectionUtils.isNotEmpty(systemConfigVos)) {
|
||||
SystemConfigVo config = systemConfigVos.stream()
|
||||
.filter(item -> key.equals(item.getConfigCode()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (Objects.nonNull(config)) {
|
||||
String useStatus = config.getUseStatus();
|
||||
if (Objects.equals("0", useStatus)) {
|
||||
isResponseEncrypt = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return isResponseEncrypt;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -35,6 +35,7 @@ public class ParamSecureHandler implements AsyncHandlerInterceptor {
|
|||
ignoreUrlPatterns.add("/smartArchives/getInfo");
|
||||
ignoreUrlPatterns.add("/smartArchives/getRouters");
|
||||
ignoreUrlPatterns.add("/smartArchives/session/check");
|
||||
ignoreUrlPatterns.add("/smartArchives/sys/config/getConfig");
|
||||
}
|
||||
private String rnd = null;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import javax.servlet.ServletException;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.bonus.common.constant.CacheConstants;
|
||||
import com.bonus.common.core.redis.RedisCache;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.core.Authentication;
|
||||
|
|
@ -31,6 +33,12 @@ public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler
|
|||
@Autowired
|
||||
private TokenService tokenService;
|
||||
|
||||
private final RedisCache redisUtil;
|
||||
|
||||
public LogoutSuccessHandlerImpl(RedisCache redisUtil) {
|
||||
this.redisUtil = redisUtil;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 退出处理
|
||||
|
|
@ -50,6 +58,8 @@ public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler
|
|||
tokenService.delLoginUser(loginUser.getToken());
|
||||
// 记录用户退出日志
|
||||
AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, MessageUtils.message("user.logout.success")));
|
||||
// 删除redis缓存数据
|
||||
redisUtil.deleteObject(CacheConstants.SYSTEM_CONFIG_VOS);
|
||||
}
|
||||
ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.success(MessageUtils.message("user.logout.success"))));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue