package com.vci.ubcs.gateway.filter;
|
|
import org.slf4j.Logger;
|
import org.slf4j.LoggerFactory;
|
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
import org.springframework.core.Ordered;
|
import org.springframework.http.HttpStatus;
|
import org.springframework.http.server.reactive.ServerHttpRequest;
|
import org.springframework.stereotype.Component;
|
import org.springframework.web.server.ServerWebExchange;
|
import reactor.core.publisher.Mono;
|
|
import javax.annotation.PostConstruct;
|
import java.util.Arrays;
|
import java.util.HashSet;
|
import java.util.Set;
|
import java.util.regex.Pattern;
|
|
/**
|
* 全局URL安全过滤器 - 对所有请求生效
|
* 直接使用@Value注解读取配置
|
*/
|
@Component
|
public class GlobalUrlSecurityFilter implements GlobalFilter, Ordered {
|
private static final Logger log = LoggerFactory.getLogger(GlobalUrlSecurityFilter.class);
|
|
// 是否启用URL安全过滤器
|
@Value("${gateway.security.url.enabled:true}")
|
private boolean enabled;
|
|
// 危险字符正则表达式
|
@Value("${gateway.security.url.dangerous-pattern:<script|</script|javascript:|onload|onerror|onclick|union.*select|select.*from|[\"';<>\\x00-\\x1F\\x7F]|%3C|%3E|%27|%22|%00|\\.\\./|\\.\\.\\\\}")
|
private String dangerousPattern;
|
|
// 白名单路径
|
@Value("${gateway.security.url.whitelist-paths:/health,/actuator/health,/actuator/info,/favicon.ico}")
|
private String[] whitelistPaths;
|
|
private Pattern compiledPattern;
|
|
private Set<String> whitelistSet;
|
|
/**
|
* 初始化方法
|
*/
|
@PostConstruct
|
public void init() {
|
try {
|
// 编译危险字符正则表达式
|
this.compiledPattern = Pattern.compile(dangerousPattern, Pattern.CASE_INSENSITIVE);
|
|
// 初始化白名单集合
|
this.whitelistSet = new HashSet<>(Arrays.asList(whitelistPaths));
|
|
log.info("全局URL安全过滤器初始化完成");
|
log.info("过滤器状态: {}", enabled ? "已启用" : "已禁用");
|
log.info("白名单路径: {}", whitelistSet);
|
|
} catch (Exception e) {
|
log.error("初始化过滤器失败", e);
|
// 使用默认配置作为后备
|
this.compiledPattern = Pattern.compile(
|
"<script|</script|javascript:|onload|onerror|onclick|union.*select|select.*from|[\"';<>\\x00-\\x1F\\x7F]|%3C|%3E|%27|%22|%00|\\.\\./|\\.\\.\\\\",
|
Pattern.CASE_INSENSITIVE
|
);
|
this.whitelistSet = new HashSet<>(Arrays.asList(
|
"/health", "/actuator/health", "/actuator/info", "/favicon.ico"
|
));
|
}
|
}
|
|
@Override
|
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
// 检查是否启用过滤器
|
if (!enabled) {
|
return chain.filter(exchange);
|
}
|
|
ServerHttpRequest request = exchange.getRequest();
|
String path = request.getURI().getPath();
|
String method = request.getMethod().name();
|
|
// 检查白名单路径
|
if (isWhitelistedPath(path)) {
|
return chain.filter(exchange);
|
}
|
|
// 验证路径安全性
|
if (!isPathSafe(path)) {
|
log.warn("拦截危险请求: {} {}", method, request.getURI());
|
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
|
return exchange.getResponse().setComplete();
|
}
|
|
return chain.filter(exchange);
|
}
|
|
@Override
|
public int getOrder() {
|
return Ordered.HIGHEST_PRECEDENCE;
|
}
|
|
/**
|
* 验证路径安全性
|
*/
|
private boolean isPathSafe(String path) {
|
if (path == null || path.isEmpty()) {
|
return true;
|
}
|
|
// 检查路径遍历
|
if (path.contains("../") || path.contains("..\\")) {
|
return false;
|
}
|
|
// 检查危险字符
|
if (compiledPattern.matcher(path).find()) {
|
return false;
|
}
|
|
return true;
|
}
|
|
/**
|
* 检查路径是否在白名单中
|
*/
|
private boolean isWhitelistedPath(String path) {
|
if (path == null) {
|
return false;
|
}
|
|
for (String whitelistPath : whitelistSet) {
|
if (path.startsWith(whitelistPath) || path.equals(whitelistPath)) {
|
return true;
|
}
|
}
|
|
return false;
|
}
|
}
|