package cn.herodotus.dante.gateway.filter;@b@@b@import cn.herodotus.dante.gateway.utils.WebFluxUtils;@b@import cn.herodotus.engine.assistant.core.utils.protect.SqlInjectionUtils;@b@import cn.herodotus.engine.assistant.definition.constants.ErrorCodes;@b@import cn.herodotus.engine.assistant.definition.domain.Result;@b@import io.netty.buffer.ByteBufAllocator;@b@import org.apache.commons.lang3.StringUtils;@b@import org.apache.hc.core5.http.HttpStatus;@b@import org.jetbrains.annotations.NotNull;@b@import org.slf4j.Logger;@b@import org.slf4j.LoggerFactory;@b@import org.springframework.cloud.gateway.filter.GatewayFilterChain;@b@import org.springframework.cloud.gateway.filter.GlobalFilter;@b@import org.springframework.core.Ordered;@b@import org.springframework.core.io.buffer.DataBuffer;@b@import org.springframework.core.io.buffer.DataBufferUtils;@b@import org.springframework.core.io.buffer.NettyDataBufferFactory;@b@import org.springframework.http.HttpHeaders;@b@import org.springframework.http.HttpMethod;@b@import org.springframework.http.MediaType;@b@import org.springframework.http.server.reactive.ServerHttpRequest;@b@import org.springframework.http.server.reactive.ServerHttpRequestDecorator;@b@import org.springframework.stereotype.Component;@b@import org.springframework.web.server.ServerWebExchange;@b@import reactor.core.publisher.Flux;@b@import reactor.core.publisher.Mono;@b@@b@import java.net.URI;@b@import java.nio.charset.StandardCharsets;@b@import java.util.concurrent.atomic.AtomicReference;@b@@b@/**@b@ * <p>Description: SQL 注入拦截过滤器 </p>@b@ * <p>@b@ * 对xss字符集的转换会导致会第三方平台接入的接口出现一些列问题,尤其是需要参数签名验签的接口,因为参数的变化导致验签不成功@b@ * 对于第三方平台(尤其时强势的第三方),我们往往无法要求第三方按照我们的参数规则传递参数,这类的接口会包含sql注入的关键字@b@ * 在请求重构过程,可能会改变参数的结构,会导致验签失败@b@ * 对post请求,虽然目前前后端大多交互都是通过Json,但如有特殊请求参数可能是非Json格式参数,需要多改类型参数进行兼容@b@ *@b@ * @author : gengwei.zheng@b@ * @date : 2021/9/1 8:47@b@ */@b@@Component@b@public class GlobalSqlInjectionFilter implements GlobalFilter, Ordered {@b@@b@ private static final Logger log = LoggerFactory.getLogger(GlobalSqlInjectionFilter.class);@b@@b@ @Override@b@ public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {@b@@b@ log.debug("[Herodotus] |- Global SQL Injection Filter in use!");@b@@b@ ServerHttpRequest serverHttpRequest = exchange.getRequest();@b@ HttpMethod method = serverHttpRequest.getMethod();@b@ String contentType = serverHttpRequest.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);@b@@b@ URI uri = exchange.getRequest().getURI();@b@@b@ if (isGetRequest(method)) {@b@ String rawQuery = uri.getRawQuery();@b@ if (StringUtils.isBlank(rawQuery)) {@b@ return chain.filter(exchange);@b@ }@b@@b@ log.debug("[Herodotus] |- The original request parameter is [{}]", rawQuery);@b@ // 执行XSS清理@b@ boolean isSQLInjection = SqlInjectionUtils.checkForGet(rawQuery);@b@@b@ // 如果存在sql注入,直接拦截请求@b@ if (isSQLInjection) {@b@ return sqlInjectionResponse(exchange, uri);@b@ }@b@ // 不对参数做任何处理@b@ return chain.filter(exchange);@b@ }@b@@b@ //post请求时,如果是文件上传之类的请求,不修改请求消息体@b@ if (isPostRequest(method, contentType)) {@b@ // 参考的是 org.springframework.cloud.gateway.filter.factory.AddRequestParameterGatewayFilterFactory@b@@b@ //从请求里获取Post请求体@b@ String bodyString = resolveBodyFromRequest(serverHttpRequest);@b@@b@ if (StringUtils.isNotBlank(bodyString)) {@b@@b@ boolean isSQLInjection;@b@ if (WebFluxUtils.isJsonMediaType(contentType)) {@b@ //如果MediaType是json才执行json方式验证@b@ isSQLInjection = SqlInjectionUtils.checkForPost(bodyString);@b@ } else {@b@ //form表单方式,需要走get请求@b@ isSQLInjection = SqlInjectionUtils.checkForGet(bodyString);@b@ }@b@@b@ // 如果存在sql注入,直接拦截请求@b@ if (isSQLInjection) {@b@ return sqlInjectionResponse(exchange, uri);@b@ }@b@@b@ ServerHttpRequest request = serverHttpRequest.mutate().uri(uri).build();@b@ // 重新构造body@b@ byte[] newBytes = bodyString.getBytes(StandardCharsets.UTF_8);@b@ DataBuffer bodyDataBuffer = toDataBuffer(newBytes);@b@ Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);@b@@b@ HttpHeaders headers = new HttpHeaders();@b@ headers.putAll(exchange.getRequest().getHeaders());@b@@b@@b@ // 由于修改了传递参数,需要重新设置CONTENT_LENGTH,长度是字节长度,不是字符串长度@b@ int length = bodyString.getBytes().length;@b@ headers.remove(HttpHeaders.CONTENT_LENGTH);@b@ headers.setContentLength(length);@b@@b@ // 设置CONTENT_TYPE@b@ if (StringUtils.isNotBlank(contentType)) {@b@ headers.set(HttpHeaders.CONTENT_TYPE, contentType);@b@ }@b@@b@ // 由于post的body只能订阅一次,由于上面代码中已经订阅过一次body。所以要再次封装请求到request才行,不然会报错请求已经订阅过@b@ request = new ServerHttpRequestDecorator(request) {@b@ @NotNull@b@ @Override@b@ public HttpHeaders getHeaders() {@b@ return headers;@b@ }@b@@b@ @NotNull@b@ @Override@b@ public Flux<DataBuffer> getBody() {@b@ return bodyFlux;@b@ }@b@ };@b@@b@ //封装request,传给下一级@b@ return chain.filter(exchange.mutate().request(request).build());@b@ }@b@ }@b@@b@ return chain.filter(exchange);@b@ }@b@@b@ /**@b@ * 从Flux<DataBuffer>中获取字符串的方法@b@ *@b@ * @return 请求体@b@ */@b@ private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) {@b@ //获取请求体@b@ Flux<DataBuffer> body = serverHttpRequest.getBody();@b@ AtomicReference<String> bodyRef = new AtomicReference<>();@b@ body.subscribe(buffer -> {@b@ byte[] content = new byte[buffer.readableByteCount()];@b@ buffer.read(content);@b@ DataBufferUtils.release(buffer);@b@ bodyRef.set(new String(content, StandardCharsets.UTF_8));@b@ });@b@ //获取request body@b@ return bodyRef.get();@b@ }@b@@b@ /**@b@ * 字节数组转DataBuffer@b@ *@b@ * @param bytes 字节数组@b@ * @return DataBuffer@b@ */@b@ private DataBuffer toDataBuffer(byte[] bytes) {@b@ NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);@b@ DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);@b@ buffer.write(bytes);@b@ return buffer;@b@ }@b@@b@ private Mono<Void> sqlInjectionResponse(ServerWebExchange exchange, URI uri) {@b@ log.error("[Herodotus] |- Parameters of Request [" + uri.getRawPath() + uri.getRawQuery() + "] contain illegal SQL keyword!");@b@ return WebFluxUtils.writeJsonResponse(exchange.getResponse(), new Result<String>().type(ErrorCodes.SQL_INJECTION_REQUEST).status(HttpStatus.SC_FORBIDDEN));@b@ }@b@@b@ private boolean isGetRequest(HttpMethod method) {@b@ return method == HttpMethod.GET;@b@ }@b@@b@ private Boolean isPostRequest(HttpMethod method, String contentType) {@b@ return (method == HttpMethod.POST || method == HttpMethod.PUT) && (MediaType.APPLICATION_FORM_URLENCODED_VALUE.equalsIgnoreCase(contentType) || MediaType.APPLICATION_JSON_VALUE.equals(contentType));@b@ }@b@@b@ /**@b@ * 自定义过滤器执行的顺序,数值越大越靠后执行,越小就越先执行@b@ *@b@ * @return order@b@ */@b@ @Override@b@ public int getOrder() {@b@ return FilterOrder.GLOBAL_SQL_INJECTION_FILTER_ORDER;@b@ }@b@}