1. 创建Filter 实现GlobalFilter, Ordered
@Slf4j
@Component
public class SqLinjectionFilter implements GlobalFilter, Ordered {

  @SneakyThrows
  @Override
  public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain){
    // grab configuration from Config object
    log.debug("----自定义防XSS攻击网关全局过滤器生效----");
    ServerHttpRequest serverHttpRequest = exchange.getRequest();
    HttpMethod method = serverHttpRequest.getMethod();
    String contentType = serverHttpRequest.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
    URI uri = exchange.getRequest().getURI();

    Boolean postFlag = (method == HttpMethod.POST || method == HttpMethod.PUT) &&
        (MediaType.APPLICATION_FORM_URLENCODED_VALUE.equalsIgnoreCase(contentType) || MediaType.APPLICATION_JSON_VALUE.equals(contentType));

    //过滤get请求
    if (method == HttpMethod.GET) {

      String rawQuery = uri.getRawQuery();
      if (StringUtils.isBlank(rawQuery)){
        return chain.filter(exchange);
      }

      log.debug("原请求参数为:{}", rawQuery);
      // 执行XSS清理
      rawQuery = XssCleanRuleUtils.xssGetClean(rawQuery);
      log.debug("修改后参数为:{}", rawQuery);

      //    如果存在sql注入,直接拦截请求
      if (rawQuery.contains("forbid")) {
        log.error("请求【" + uri.getRawPath() + uri.getRawQuery() + "】参数中包含不允许sql的关键词, 请求拒绝");
        return setUnauthorizedResponse(exchange);
      }

      try {
        //重新构造get request
        URI newUri = UriComponentsBuilder.fromUri(uri)
            .replaceQuery(rawQuery)
            .build(true)
            .toUri();

        ServerHttpRequest request = exchange.getRequest().mutate()
            .uri(newUri).build();
        return chain.filter(exchange.mutate().request(request).build());
      } catch (Exception e) {
        log.error("get请求清理xss攻击异常", e);
        throw new IllegalStateException("Invalid URI query: \"" + rawQuery + "\"");
      }
    }
    //post请求时,如果是文件上传之类的请求,不修改请求消息体
    else if (postFlag){

      return DataBufferUtils.join(serverHttpRequest.getBody()).flatMap(d -> Mono.just(Optional.of(d))).defaultIfEmpty(
          Optional.empty())
          .flatMap(optional -> {
            // 取出body中的参数
            String bodyString = "";
            if (optional.isPresent()) {
              byte[] oldBytes = new byte[optional.get().readableByteCount()];
              optional.get().read(oldBytes);
              bodyString = new String(oldBytes, StandardCharsets.UTF_8);
            }
            HttpHeaders httpHeaders = serverHttpRequest.getHeaders();
            // 执行XSS清理
            log.debug("{} - [{}:{}] XSS处理前参数:{}", method, uri.getPath(), bodyString);
            bodyString = XssCleanRuleUtils.xssPostClean(bodyString);
            log.info("{} - [{}:{}] XSS处理后参数:{}", method, uri.getPath(), bodyString);

            //  如果存在sql注入,直接拦截请求
            if (bodyString.contains("forbid")) {
              log.error("{} - [{}:{}] 参数:{}, 包含不允许sql的关键词,请求拒绝", method, uri.getPath(), bodyString);
              return setUnauthorizedResponse(exchange);
            }

            ServerHttpRequest newRequest = serverHttpRequest.mutate().uri(uri).build();

            // 重新构造body
            byte[] newBytes = bodyString.getBytes(StandardCharsets.UTF_8);
            DataBuffer bodyDataBuffer = toDataBuffer(newBytes);
            Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);

            // 重新构造header
            HttpHeaders headers = new HttpHeaders();
            headers.putAll(httpHeaders);
            // 由于修改了传递参数,需要重新设置CONTENT_LENGTH,长度是字节长度,不是字符串长度
            int length = newBytes.length;
            headers.remove(HttpHeaders.CONTENT_LENGTH);
            headers.setContentLength(length);
            headers.set(HttpHeaders.CONTENT_TYPE, "application/json;charset=utf8");
            // 重写ServerHttpRequestDecorator,修改了body和header,重写getBody和getHeaders方法
            newRequest = new ServerHttpRequestDecorator(newRequest) {
              @Override
              public Flux<DataBuffer> getBody() {
                return bodyFlux;
              }

              @Override
              public HttpHeaders getHeaders() {
                return headers;
              }
            };

            return chain.filter(exchange.mutate().request(newRequest).build());
          });
    } else {
      return chain.filter(exchange);
    }

  }


  // 自定义过滤器执行的顺序,数值越大越靠后执行,越小就越先执行
  @Override
  public int getOrder() {
    return Ordered.HIGHEST_PRECEDENCE;
  }

  /**
   * 设置403拦截状态
   */
  private Mono<Void> setUnauthorizedResponse(ServerWebExchange exchange) {
    return WebfluxResponseUtil.responseFailed(exchange, HttpStatus.FORBIDDEN.value(),
        "request is forbidden, SQL keywords are not allowed in the parameters.");
  }

  /**
   * 字节数组转DataBuffer
   *
   * @param bytes 字节数组
   * @return DataBuffer
   */
  private DataBuffer toDataBuffer(byte[] bytes) {
    NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
    DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
    buffer.write(bytes);
    return buffer;
  }

}

 

  1. 定义xss注入、sql注入工具类
@Slf4j
public class XssCleanRuleUtils {

  private final static Pattern[] scriptPatterns = {
      Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE),
      Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
      Pattern.compile("</script>", Pattern.CASE_INSENSITIVE),
      Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
      Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
      Pattern.compile("expression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
      Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE),
      Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE),
      Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL)
  };

  private static String badStrReg = "\\b(and|or)\\b.{1,6}?(=|>|<|\\bin\\b|\\blike\\b)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";

  private static Pattern sqlPattern = Pattern.compile(badStrReg, Pattern.CASE_INSENSITIVE);//整体都忽略大小写

  /**
   * GET请求参数过滤
   * @param value
   * @return
   */
  public static String xssGetClean(String value) throws UnsupportedEncodingException {

    //过滤xss字符集
    if (value != null) {
      value = value.replaceAll("\0|\n|\r", "");
      for (Pattern pattern : scriptPatterns) {
        value = pattern.matcher(value).replaceAll("");
      }
      value = value.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
    }

    //sql关键字检查
    return cleanGetSqlKeyWords(value);

  }


  public static String xssPostClean(String value) {

    //过滤xss字符集
    if (value != null) {
      value = value.replaceAll("\0|\n|\r", "");
      for (Pattern pattern : scriptPatterns) {
        value = pattern.matcher(value).replaceAll("");
      }
      value = value.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
    }
    //sql关键字检查
    return cleanPostSqlKeyWords(value);

  }

  /**
   * 解析参数SQL关键字
   * @param value
   * @return
   */
  private static String cleanGetSqlKeyWords(String value) throws UnsupportedEncodingException {

    //参数需要url编码
    //这里需要将参数转换为小写来处理
    //不改变原值
    //value示例 order=asc&pageNum=1&pageSize=100&parentId=0
    String lowerValue = URLDecoder.decode(value, "UTF-8").toLowerCase();

    //获取到请求中所有参数值-取每个key=value组合第一个等号后面的值
    boolean isContains = Stream.of(lowerValue.split("\\&"))
        .map(kp -> kp.substring(kp.indexOf("=") + 1))
        .parallel()
        .anyMatch(param -> {
          if (sqlPattern.matcher(param).find())
          {
            log.error("参数中包含不允许sql的关键词");
            return true;
          }
          return false;
        });

    return isContains ? "forbid" : value;
  }


  /**
   * 解析参数SQL关键字
   * @param value
   * @return
   */
  private static String cleanPostSqlKeyWords(String value){

    JSONObject json = JSONObject.parseObject(value);
    Map<String, Object> map = json;
    Map<String, Object> mapjson = new HashMap<>();

    for (Map.Entry<String, Object> entry : map.entrySet()) {
      String value1 =  entry.getValue().toString();

      //这里需要将参数转换为小写来处理-不改变原值
      String lowerValue = value1.toLowerCase();

      if (sqlPattern.matcher(lowerValue).find())
      {
        log.error("参数中包含不允许sql的关键词");
        value1 = "forbid";
        mapjson.put(entry.getKey(),value1);
        break;
      } else {
        mapjson.put(entry.getKey(),entry.getValue());
      }

    }

    return JSONObject.toJSONString(mapjson);
  }

 

 

 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐