Skip to content

自定义实现敏感词过滤

约 895 字大约 3 分钟

敏感词过滤

2025-07-29

一、适用于低版本 Spring Boot 项目的敏感词过滤器

1. 敏感词过滤器类 SensitiveWordsFilter

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.*;

@Component
@Slf4j
@WebFilter(urlPatterns = "/*", filterName = "SensitiveWordsFilter")
public class SensitiveWordsFilter implements Filter {

    // 敏感词列表
    private final List<String> sensitiveWords = Arrays.asList("insert ", "update ", "delete ", "create ",
            "alter ", "drop ", "truncate ");

    @Override
    public void init(FilterConfig filterConfig) {
        // 可选初始化逻辑
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;

        // 检查请求头
        boolean hasSensitiveHeader = Collections.list(httpRequest.getHeaderNames()).stream()
                .anyMatch(header -> containsSensitiveWord(httpRequest.getHeader(header)));
        if (hasSensitiveHeader) {
            log.error("请求头中存在敏感词");
        }

        // 检查请求体(仅对 POST 请求处理)
        String method = httpRequest.getMethod();
        if (StringPool.POST.equalsIgnoreCase(method)) {
            SensitiveWordsRequestWrapper requestWrapper = new SensitiveWordsRequestWrapper(httpRequest);
            String body = requestWrapper.getBody();
            if (containsSensitiveWord(body)) {
                log.error("请求体中存在敏感词");
            }
        }

        // 检查请求参数
        Map<String, String[]> parameterMap = httpRequest.getParameterMap();
        boolean hasSensitiveParameter = parameterMap.values().stream()
                .flatMap(Arrays::stream)
                .anyMatch(this::containsSensitiveWord);
        if (hasSensitiveParameter) {
            log.error("请求参数中存在敏感词");
        }

        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        // 可选清理逻辑
    }

    private boolean containsSensitiveWord(String text) {
        if (StringUtil.isNotBlank(text)) {
            text = text.toLowerCase();
            return sensitiveWords.stream().anyMatch(text::contains);
        }
        return false;
    }
}

2. 请求体包装类 SensitiveWordsRequestWrapper

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class SensitiveWordsRequestWrapper extends HttpServletRequestWrapper {
private final String body;

    public SensitiveWordsRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        StringBuilder sb = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8))) {
            char[] buffer = new char[512];
            int bytesRead;
            while ((bytesRead = reader.read(buffer)) > 0) {
                sb.append(buffer, 0, bytesRead);
            }
        }
        this.body = sb.toString();
    }

    public String getBody() {
        return this.body;
    }
}

3. 常量类 StringPool

public interface StringPool {
    String AMPERSAND = "&";
    String AND = "and";
    String AT = "@";
    String ASTERISK = "*";
    String STAR = "*";
    String SLASH = "/";
    String BACK_SLASH = "\\";
    String DOUBLE_SLASH = "#//";
    String COLON = ":";
    String COMMA = ",";
    String DASH = "-";
    String DOLLAR = "$";
    String DOT = ".";
    String EMPTY = "";
    String EMPTY_JSON = "{}";
    String EQUALS = "=";
    String FALSE = "false";
    String HASH = "#";
    String HAT = "^";
    String LEFT_BRACE = "{";
    String LEFT_BRACKET = "(";
    String LEFT_CHEV = "<";
    String NEWLINE = "\n";
    String N = "n";
    String NO = "no";
    String NULL = "null";
    String OFF = "off";
    String ON = "on";
    String PERCENT = "%";
    String PIPE = "|";
    String PLUS = "+";
    String QUESTION_MARK = "?";
    String EXCLAMATION_MARK = "!";
    String QUOTE = "\"";
    String RETURN = "\r";
    String TAB = "\t";
    String RIGHT_BRACE = "}";
    String RIGHT_BRACKET = ")";
    String RIGHT_CHEV = ">";
    String SEMICOLON = ";";
    String SINGLE_QUOTE = "'";
    String BACKTICK = "`";
    String SPACE = " ";
    String TILDA = "~";
    String LEFT_SQ_BRACKET = "[";
    String RIGHT_SQ_BRACKET = "]";
    String TRUE = "true";
    String UNDERSCORE = "_";
    String UTF_8 = "UTF-8";
    String GBK = "GBK";
    String ISO_8859_1 = "ISO-8859-1";
    String Y = "y";
    String YES = "yes";
    String ONE = "1";
    String ZERO = "0";
    String MINUS_ONE = "-1";
    String DOLLAR_LEFT_BRACE = "${";
    String UNKNOWN = "unknown";
    String GET = "GET";
    String POST = "POST";
}

二、适用于 Spring Cloud Gateway 的全局敏感词过滤方案

1. 请求体预读取过滤器 GetBodyGlobalFilter

@Component
@AllArgsConstructor
public class GetBodyGlobalFilter implements GlobalFilter, Ordered {

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
   if (HttpMethod.POST.equals(exchange.getRequest().getMethod()) && MediaType.APPLICATION_JSON.equalsTypeAndSubtype(exchange.getRequest().getHeaders().getContentType())) {
      return DataBufferUtils.join(exchange.getRequest().getBody())
         .flatMap(dataBuffer -> {
            DataBufferUtils.retain(dataBuffer);
            Flux<DataBuffer> cachedFlux = Flux
               .defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));
            ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(
               exchange.getRequest()) {
               @Override
               public Flux<DataBuffer> getBody() {
                  return cachedFlux;
               }
            };
            return chain.filter(exchange.mutate().request(mutatedRequest).build());
         });
   } else {
      return chain.filter(exchange);
   }
}

@Override
public int getOrder() {
   return -102;
}
}

2. 敏感词检测过滤器 SensitiveWordsFilter

/**
 * 敏感词检测
 *
 */
@Slf4j
@Component
@AllArgsConstructor
public class SensitiveWordsFilter implements GlobalFilter, Ordered {

	private final ObjectMapper objectMapper;

	// 敏感词列表
	private final List<String> sensitiveWords = Arrays.asList("insert ", "update ", "delete ", "create ",
			"alter ", "drop ", "truncate ");

	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		ServerHttpRequest request = exchange.getRequest();
		ServerHttpResponse resp = exchange.getResponse();

		// 检查请求头中是否包含敏感词
		boolean hasSensitiveHeader = request.getHeaders().entrySet().stream()
				.anyMatch(entry -> entry.getValue().stream().anyMatch(this::containsSensitiveWord));
		if (hasSensitiveHeader) {
			return unAuth(resp, "请求头中存在敏感词");
		}

		if (HttpMethod.POST.equals(exchange.getRequest().getMethod()) && MediaType.APPLICATION_JSON.equalsTypeAndSubtype(exchange.getRequest().getHeaders().getContentType())) {
			// 检查请求体中是否包含敏感词
			Flux<DataBuffer> body = request.getBody();
			AtomicReference<String> bodyRef = new AtomicReference<>();
			body.subscribe(buffer -> {
				CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
				DataBufferUtils.release(buffer);
				bodyRef.set(charBuffer.toString());
			});
			String rbody = bodyRef.get();
			if (containsSensitiveWord(rbody)) {
				return unAuth(resp, "请求体中存在敏感词");
			}
		}

		// 检查请求参数中是否包含敏感词
		String queryParams = request.getURI().getQuery();
		if (containsSensitiveWord(queryParams)) {
			return unAuth(resp, "请求参数中存在敏感词");
		}

		// 如果没有敏感词,继续处理请求
		return chain.filter(exchange);
	}

	private Mono<Void> unAuth(ServerHttpResponse resp, String msg) {
		resp.setStatusCode(HttpStatus.UNAUTHORIZED);
		resp.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
		String result = "";
		try {
			result = objectMapper.writeValueAsString(ResponseProvider.unAuth(msg));
		} catch (JsonProcessingException e) {
			log.error(e.getMessage(), e);
		}
		DataBuffer buffer = resp.bufferFactory().wrap(result.getBytes(StandardCharsets.UTF_8));
		return resp.writeWith(Flux.just(buffer));
	}

	private boolean containsSensitiveWord(String text) {
		if (StringUtils.isNotBlank(text)) {
			text = text.toLowerCase();
			for (String sensitiveWord : sensitiveWords) {
				if (text.contains(sensitiveWord)) {
					return true;
				}
			}
		}
		return false;
	}

	@Override
	public int getOrder() {
		return -101;
	}
}

贡献者

  • flycodeuflycodeu

公告板

2025-03-04正式迁移知识库到此项目