diff --git a/common/common-security/src/main/java/com/bonus/common/security/service/TokenService.java b/common/common-security/src/main/java/com/bonus/common/security/service/TokenService.java index a2c1411..bf29383 100644 --- a/common/common-security/src/main/java/com/bonus/common/security/service/TokenService.java +++ b/common/common-security/src/main/java/com/bonus/common/security/service/TokenService.java @@ -65,7 +65,7 @@ public class TokenService redisService.deleteObject(oldTokenKey); } // 存储新的token关联 - redisService.setCacheObject(userTokenKey, token, expireTime, TimeUnit.SECONDS); + redisService.setCacheObject(userTokenKey, token, expireTime, TimeUnit.MINUTES); String userKey = getTokenKey(loginUser.getToken()); // Jwt存储信息 Map claimsMap = new HashMap(); diff --git a/gateway/src/main/java/com/bonus/gateway/config/XssFilterUtil.java b/gateway/src/main/java/com/bonus/gateway/config/XssFilterUtil.java new file mode 100644 index 0000000..8458458 --- /dev/null +++ b/gateway/src/main/java/com/bonus/gateway/config/XssFilterUtil.java @@ -0,0 +1,50 @@ +package com.bonus.gateway.config; + +/** + * @author 马三炮 + * @date 2025/11/26 + */ + + +import org.apache.commons.lang3.StringEscapeUtils; + +/** + * XSS过滤工具类 + */ +public class XssFilterUtil { + + /** + * 过滤特殊字符(黑名单方式) + */ + public static String filterSpecialChars(String input) { + if (input == null || input.isEmpty()) { + return input; + } + // 替换需过滤的字符(可根据需求调整替换策略:清空或转义) + return input.replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll("\"", """) + .replaceAll("'", "'") + .replaceAll("%", "%") + .replaceAll(";", ";") + .replaceAll("\\(", "(") + .replaceAll("\\)", ")") + .replaceAll("\\+", "+"); + } + + /** + * HTML转义(推荐优先使用) + * 基于Apache Commons Text工具类,更全面的转义 + */ + public static String htmlEscape(String input) { + return StringEscapeUtils.escapeHtml4(input); + } + + /** + * 组合过滤:先黑名单过滤,再HTML转义 + */ + public static String xssFilter(String input) { + String filtered = filterSpecialChars(input); + return htmlEscape(filtered); + } +} diff --git a/gateway/src/main/java/com/bonus/gateway/config/XssRequestWrapper.java b/gateway/src/main/java/com/bonus/gateway/config/XssRequestWrapper.java new file mode 100644 index 0000000..944d9a7 --- /dev/null +++ b/gateway/src/main/java/com/bonus/gateway/config/XssRequestWrapper.java @@ -0,0 +1,51 @@ +package com.bonus.gateway.config; + +/** + * @author 马三炮 + * @date 2025/11/26 + */ +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.util.Map; + +public class XssRequestWrapper extends HttpServletRequestWrapper { + + public XssRequestWrapper(HttpServletRequest request) { + super(request); + } + + @Override + public String getParameter(String name) { + String value = super.getParameter(name); + return XssFilterUtil.xssFilter(value); + } + + @Override + public String[] getParameterValues(String name) { + String[] values = super.getParameterValues(name); + if (values == null) { + return null; + } + for (int i = 0; i < values.length; i++) { + values[i] = XssFilterUtil.xssFilter(values[i]); + } + return values; + } + + @Override + public Map getParameterMap() { + Map map = super.getParameterMap(); + map.forEach((key, values) -> { + for (int i = 0; i < values.length; i++) { + values[i] = XssFilterUtil.xssFilter(values[i]); + } + }); + return map; + } + + @Override + public String getHeader(String name) { + String value = super.getHeader(name); + return XssFilterUtil.xssFilter(value); + } +} diff --git a/gateway/src/main/java/com/bonus/gateway/filter/XssFilter.java b/gateway/src/main/java/com/bonus/gateway/filter/XssFilter.java index 5cc2c27..98e44f1 100644 --- a/gateway/src/main/java/com/bonus/gateway/filter/XssFilter.java +++ b/gateway/src/main/java/com/bonus/gateway/filter/XssFilter.java @@ -2,8 +2,10 @@ package com.bonus.gateway.filter; import com.bonus.common.core.utils.StringUtils; import com.bonus.common.core.utils.html.EscapeUtil; +import com.bonus.gateway.config.XssFilterUtil; import com.bonus.gateway.config.properties.XssProperties; import io.netty.buffer.ByteBufAllocator; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cloud.gateway.filter.GatewayFilterChain; @@ -20,6 +22,8 @@ import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; + +import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; /** @@ -27,6 +31,7 @@ import java.nio.charset.StandardCharsets; * * @author zys */ +@Slf4j @Component @ConditionalOnProperty(value = "security.xss.enabled", havingValue = "true") public class XssFilter implements GlobalFilter, Ordered @@ -46,10 +51,10 @@ public class XssFilter implements GlobalFilter, Ordered return chain.filter(exchange); } // 非json类型,不过滤 - if (!isJsonRequest(exchange)) + /*if (!isJsonRequest(exchange)) { return chain.filter(exchange); - } + }*/ // excludeUrls 不过滤 String url = request.getURI().getPath(); if (StringUtils.matches(url, xss.getExcludeUrls())) @@ -76,8 +81,14 @@ public class XssFilter implements GlobalFilter, Ordered join.read(content); DataBufferUtils.release(join); String bodyStr = new String(content, StandardCharsets.UTF_8); + try { + bodyStr = java.net.URLDecoder.decode(bodyStr, StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } // 防xss攻击过滤 bodyStr = EscapeUtil.clean(bodyStr); + bodyStr = XssFilterUtil.filterSpecialChars(bodyStr); // 转成字节 byte[] bytes = bodyStr.getBytes(); NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT); @@ -104,7 +115,7 @@ public class XssFilter implements GlobalFilter, Ordered /** * 是否是Json请求 - * + * * @param exchange HTTP请求 */ public boolean isJsonRequest(ServerWebExchange exchange)