美文网首页Spring Cloud
Spring Cloud Gateway 实现XSS、SQL注入

Spring Cloud Gateway 实现XSS、SQL注入

作者: 蜀山_竹君子 | 来源:发表于2020-11-13 12:08 被阅读0次

    XSS和SQL注入是Web应用中常见计算机安全漏洞,文章主要分享通过Spring Cloud Gateway 全局过滤器对XSS和SQL注入进行安全防范。

    使用版本

    • spring-cloud-dependencies Hoxton.SR7
    • spring-boot-dependencies 2.2.9.RELEASE
    • spring-cloud-gateway 2.2.4.RELEASE

    核心技术点

    1. AddRequestParameterGatewayFilterFactory 获取get请求参数并添加参数然后重构get请求
    public GatewayFilter apply(NameValueConfig config) {
        return new GatewayFilter() {
          public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            URI uri = exchange.getRequest().getURI();
            StringBuilder query = new StringBuilder();
            //获取请求url携带的参数,?号后面参数体,类似cl=3&tn=baidutop10&fr=top1000&wd=31 
            String originalQuery = uri.getRawQuery();
            if (StringUtils.hasText(originalQuery)) {
              query.append(originalQuery);
              if (originalQuery.charAt(originalQuery.length() - 1) != '&') {
                query.append('&');
              }
            }
    
            String value = ServerWebExchangeUtils.expand(exchange, config.getValue());
            query.append(config.getName());
            query.append('=');
            query.append(value);
    
            try {
              //重构请求uri
              URI newUri = UriComponentsBuilder.fromUri(uri).replaceQuery(query.toString()).build(true).toUri();
              ServerHttpRequest request = exchange.getRequest().mutate().uri(newUri).build();
              return chain.filter(exchange.mutate().request(request).build());
            } catch (RuntimeException var9) {
              throw new IllegalStateException("Invalid URI query: \"" + query.toString() + "\"");
            }
          }
    
          public String toString() {
            return GatewayToStringStyler.filterToStringCreator(AddRequestParameterGatewayFilterFactory.this).append(config.getName(), config.getValue()).toString();
          }
        };
      }
    
    1. Spring Cloud Gateway中RequestBody只能获取一次的问题解决方案
    2. Spring Gateway GlobalFilter

    技术实现

    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);
      }
    

    踩坑过程

    1. sql注入过滤规则
      网上大多数sql注入拦截规则都是使用一个sql关键字匹配,
    //定义sql注入关键字
    String badStr = "'|and|exec|execute|insert|select|delete|update|count|drop|%|chr|mid|master|truncate|" +
                        "char|declare|sitename|net user|xp_cmdshell|;|or|+|,|like'|and|exec|execute|insert|create|drop|" +
                        "table|from|grant|use|group_concat|column_name|" +
                        "information_schema.columns|table_schema|union|where|select|delete|update|order|by|count|" +
                        "chr|mid|master|truncate|char|declare|or|;|--|,|like|//|/|%|#";
    //过滤规则
    for (String bad : badStrs) {
                    if (value1.equalsIgnoreCase(bad)) {
                        value1 = "forbid";
                        mapjson.put(entry.getKey(),value1);
                        break;
                    } else {
                        mapjson.put(entry.getKey(),entry.getValue());
                    }
                }
            }
    

    最初我们也是使用改方式,但是关键字匹配方式实在太容易误杀正常业务,且容易漏,比如

    select/*/1from/*/tt 
    

    这样形式的参数就无法过滤。
    最后我们还是采取sql正则匹配的方式(见代码),已和安全工程师完成联调,能够挡住安全工程师的注入测试案例,对业务也完成回归测试,基本不影响现有业务正常运行。

    1. get请求拦截过程不要对源参数进行url编码,否则应用可能出现不必要的错误

    get请求参数中的中文字符以及一些特色字符请求到服务器会自动编码,在对xss注入过滤过程需要进行url编码才能进行过滤规则的验证,在起初我们是在源参数上进行编码,但是一些正常请求中携带+号这样的特色符号的请求在处理过程会被过滤掉,该问题排查了许久才发现的,因此建议不要改变源请求参数的编码格式

     //参数需要url编码
        //这里需要将参数转换为小写来处理
        //不改变原值
        //value示例 order=asc&pageNum=1&pageSize=100&parentId=0
        String lowerValue = URLDecoder.decode(value, "UTF-8").toLowerCase();
    
    1. RequestBody只能获取一次的问题
      代码参考了网上对于RequestBody只能获取一次的问题解决的方案,在spring-cloud-gateway 2.2.4.RELEASE验证有效

    优化

    拦截器在实际生产运行过程存在一些列问题:

    1. 对xss字符集的转换会导致会第三方平台接入的接口出现一些列问题,尤其是需要参数签名验签的接口,因为参数的变化导致验签不成功
    2. 对于第三方平台(尤其时强势的第三方),我们往往无法要求第三方按照我们的参数规则传递参数,这类的接口会包含sql注入的关键字
    3. 在请求重构过程,可能会改变参数的结构,会导致验签失败
    4. 对post请求,虽然目前前后端大多交互都是通过Json,但如有特殊请求参数可能是非Json格式参数,需要多改类型参数进行兼容

    因此,在实现XSS、SQL注入拦截基础上进行优化,移除xss字符集转换且不改变请求参数,增加白名单机制,具体实现如下:

    @Component
    @ConfigurationProperties(prefix = "gateway.security.ignore")
    @RefreshScope
    public class SqLinjectionFilter implements GlobalFilter, Ordered {
    
      private String[] sqlinjectionHttpUrls = new String[0];
    
      @SneakyThrows
      @Override
      public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain){
        // grab configuration from Config object
        log.debug("----自定义防sql注入网关全局过滤器生效----");
        ServerHttpRequest serverHttpRequest = exchange.getRequest();
        HttpMethod method = serverHttpRequest.getMethod();
        String contentType = serverHttpRequest.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
        URI uri = exchange.getRequest().getURI();
    
        //1.动态刷新 sql注入的过滤的路径
        String path = serverHttpRequest.getURI().getRawPath();
        String matchUrls[] = this.getSqlinjectionHttpUrls();
    
        if( AuthUtils.isMatchPath(path, matchUrls)){
          log.error("请求【{}】在sql注入过滤白名单中,直接放行", path);
          return chain.filter(exchange);
        }
    
        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);
          // 执行sql注入校验清理
          boolean chkRet = SqLinjectionRuleUtils.getRequestSqlKeyWordsCheck(rawQuery);
    
          //    如果存在sql注入,直接拦截请求
          if (chkRet) {
            log.error("请求【" + uri.getRawPath() + uri.getRawQuery() + "】参数中包含不允许sql的关键词, 请求拒绝");
            return setUnauthorizedResponse(exchange);
          }
          //透传参数,不对参数做任何处理
          return chain.filter(exchange);
        }
        //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("{} - [{}] 请求参数:{}", method, uri.getPath(), bodyString);
                boolean chkRet = false;
                if (MediaType.APPLICATION_JSON_VALUE.equals(contentType)) {
                  //如果MediaType是json才执行json方式验证
                  chkRet = SqLinjectionRuleUtils.postRequestSqlKeyWordsCheck(bodyString);
                } else {
                  //form表单方式,需要走get请求
                  chkRet = SqLinjectionRuleUtils.getRequestSqlKeyWordsCheck(bodyString);
                }
    
                //  如果存在sql注入,直接拦截请求
                if (chkRet) {
                  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, contentType);
                // 重写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;
      }
    
      public String[] getSqlinjectionHttpUrls() {
        return sqlinjectionHttpUrls;
      }
    
      public void setSqlinjectionHttpUrls(String[] sqlinjectionHttpUrls) {
        this.sqlinjectionHttpUrls = sqlinjectionHttpUrls;
      }
    }
    
    @Slf4j
    public class SqLinjectionRuleUtils {
    
      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请求sql注入校验
       * @param value
       * @return
       */
      public static boolean getRequestSqlKeyWordsCheck(String value) throws UnsupportedEncodingException {
    
        //参数需要url编码
        //这里需要将参数转换为小写来处理
        //不改变原值
        //value示例 order=asc&pageNum=1&pageSize=100&parentId=0
        String lowerValue = URLDecoder.decode(value, "UTF-8").toLowerCase();
    
        //获取到请求中所有参数值-取每个key=value组合第一个等号后面的值
        return 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;
            });
      }
    
    
      /**
       * post请求sql注入校验
       * @param value
       * @return
       */
      public static boolean postRequestSqlKeyWordsCheck(String value){
    
        Object jsonObj = JSON.parse(value);
        if (jsonObj instanceof JSONObject) {
          JSONObject json = (JSONObject) jsonObj;
          Map<String, Object> map = json;
    
          //对post请求参数值进行sql注入检验
          return map.entrySet().stream().parallel().anyMatch(entry -> {
    
            //这里需要将参数转换为小写来处理
            String lowerValue = Optional.ofNullable(entry.getValue())
                .map(Object::toString)
                .map(String::toLowerCase)
                .orElse("");
    
    
            if (sqlPattern.matcher(lowerValue).find())
            {
              log.error("参数[{}]中包含不允许sql的关键词", lowerValue);
              return true;
            }
            return false;
          });
        } else {
          JSONArray json = (JSONArray) jsonObj;
          List<Object> list = json;
          //对post请求参数值进行sql注入检验
          return list.stream().parallel().anyMatch(obj -> {
    
            //这里需要将参数转换为小写来处理
            String lowerValue = Optional.ofNullable(obj)
                .map(Object::toString)
                .map(String::toLowerCase)
                .orElse("");
    
    
            if (sqlPattern.matcher(lowerValue).find())
            {
              log.error("参数[{}]中包含不允许sql的关键词", lowerValue);
              return true;
            }
            return false;
          });
        }
    
    
    }
    

    ps:网关全局拦截影响应用所有请求,拦截规则和对请求类型的兼容还需要根据项目线上实际情况进行调整。

    AuthUtils 和 WebfluxResponseUtil只是简单工具类,AuthUtils主要是用于正则匹配,WebfluxResponseUtil则是处理WebFlux响应,这里将工具类提供出来。

    WebfluxResponseUtil

    public class WebfluxResponseUtil {
        /**
         * webflux的response返回json对象
         */
        public static Mono<Void> responseWriter(Boolean success, ServerWebExchange exchange, int httpStatus, String msg) {
            Result result = Result.of(success, null, httpStatus, msg, null);
            return responseWrite(exchange, httpStatus, result);
        }
    
        public static Mono<Void> responseFailed(ServerWebExchange exchange, String msg) {
            Result result = Result.failed(msg);
            return responseWrite(exchange, HttpStatus.INTERNAL_SERVER_ERROR.value(), result);
        }
    
        public static Mono<Void> responseFailed(ServerWebExchange exchange, int code, int httpStatus, String msg) {
            Result result = Result.failed(code, msg, null);
            return responseWrite(exchange, httpStatus, result);
        }
    
        public static Mono<Void> responseFailed(ServerWebExchange exchange, int httpStatus, String msg) {
            Result result = Result.failed(httpStatus, msg, null);
            return responseWrite(exchange, httpStatus, result);
        }
    
        public static Mono<Void> responseWrite(ServerWebExchange exchange, int httpStatus, Result result) {
            if (httpStatus == 0) {
                httpStatus = HttpStatus.INTERNAL_SERVER_ERROR.value();
            }
            ServerHttpResponse response = exchange.getResponse();
            response.getHeaders().setAccessControlAllowCredentials(true);
            response.getHeaders().setAccessControlAllowOrigin("*");
            response.setStatusCode(HttpStatus.valueOf(httpStatus));
            response.getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8);
            DataBufferFactory dataBufferFactory = response.bufferFactory();
            DataBuffer buffer = dataBufferFactory.wrap(JSONObject.toJSONString(result).getBytes(Charset.defaultCharset()));
            return response.writeWith(Mono.just(buffer)).doOnError((error) -> {
                DataBufferUtils.release(buffer);
            });
        }
    }
    

    AuthUtils

    @Slf4j
    public class AuthUtils {
        private AuthUtils() {
            throw new IllegalStateException("Utility class");
        }
    
        private static final String BASIC_ = "Basic ";
    
        /**
         * 获取requet(head/param)中的token
         * @param request
         * @return
         */
        public static String extractToken(HttpServletRequest request) {
            String token = extractHeaderToken(request);
            if (token == null) {
                token = request.getParameter(OAuth2AccessToken.ACCESS_TOKEN);
                if (token == null) {
                    log.debug("Token not found in request parameters.  Not an OAuth2 request.");
                }
            }
            return token;
        }
    
        /**
         * 解析head中的token
         * @param request
         * @return
         */
        private static String extractHeaderToken(HttpServletRequest request) {
            Enumeration<String> headers = request.getHeaders(CommonConstant.TOKEN_HEADER);
            while (headers.hasMoreElements()) {
                String value = headers.nextElement();
                if ((value.startsWith(OAuth2AccessToken.BEARER_TYPE))) {
                    String authHeaderValue = value.substring(OAuth2AccessToken.BEARER_TYPE.length()).trim();
                    int commaIndex = authHeaderValue.indexOf(',');
                    if (commaIndex > 0) {
                        authHeaderValue = authHeaderValue.substring(0, commaIndex);
                    }
                    return authHeaderValue;
                }
            }
            return null;
        }
    
        /**
         * *从header 请求中的clientId:clientSecret
         */
        public static String[] extractClient(HttpServletRequest request) {
            String header = request.getHeader("Authorization");
            if (Objects.isNull(header) || !header.startsWith(BASIC_)) {
                throw new UnapprovedClientAuthenticationException("the request header Authorization Basic is null.");
            }
            return extractHeaderClient(header);
        }
    
        /**
         * 从header 请求中的clientId:clientSecret
         *
         * @param header header中的参数
         */
        public static String[] extractHeaderClient(String header) {
            byte[] base64Client = header.substring(BASIC_.length()).getBytes(StandardCharsets.UTF_8);
            byte[] decoded = Base64.getDecoder().decode(base64Client);
            String clientStr = new String(decoded, StandardCharsets.UTF_8);
            String[] clientArr = clientStr.split(":");
            if (clientArr.length != 2) {
                throw new BusinessException("Invalid basic authentication token.");
            }
            return clientArr;
        }
    
        /**
         * 获取登陆的用户名
         */
        public static String getUsername(Authentication authentication) {
            Object principal = authentication.getPrincipal();
            String username = null;
            if (principal instanceof LoginAppUser) {
                username = ((LoginAppUser) principal).getUsername();
            } else if (principal instanceof String) {
                username = (String) principal;
            }
            return username;
        }
    
        /**
         * 通配符匹配要过滤的路径
         * @param path 要匹配的路径
         * @param urls Spring通配符路径
         * @return
         */
        public static boolean isMatchPath(String path, String... urls){
            boolean isMatchPath = Arrays.stream(urls).anyMatch(igUrl -> {
                PathMatcher pm = new AntPathMatcher();
                return pm.match(igUrl, path);
            });
            return isMatchPath;
        }
    }
    # Result 是我们项目自己使用的响应结果实体,仅供参考
    @JsonInclude(Include.NON_NULL)
    public class Result<T> implements Serializable {
        private Boolean success;
        private Integer code;
        private T data;
        private String message;
        private Throwable throwable;
    
        public static <T> Result<T> succeed() {
            return of(true, (Object)null, CodeEnum.SUCCESS.getCode(), "成功", (Throwable)null);
        }
    
        public static <T> Result<T> succeed(String msg) {
            return of(true, (Object)null, CodeEnum.SUCCESS.getCode(), msg, (Throwable)null);
        }
    
        public static <T> Result<T> succeed(T data, String msg) {
            return of(true, data, CodeEnum.SUCCESS.getCode(), msg, (Throwable)null);
        }
    
        public static <T> Result<T> succeed(T data) {
            return of(true, data, CodeEnum.SUCCESS.getCode(), (String)null, (Throwable)null);
        }
    
        public static <T> Result<T> of(Boolean success, T data, Integer code, String msg, Throwable t) {
            return new Result(success, code, data, msg, t);
        }
    
        /** @deprecated */
        @Deprecated
        public static <T> Result<T> failed(String msg) {
            return of(false, (Object)null, CodeEnum.ERROR.getCode(), msg, (Throwable)null);
        }
    
        /** @deprecated */
        @Deprecated
        public static <T> Result<T> failed(Integer code, String msg) {
            return of(false, (Object)null, code, msg, (Throwable)null);
        }
    
        public static <T> Result<T> failed(T data, String msg) {
            return of(false, data, CodeEnum.ERROR.getCode(), msg, (Throwable)null);
        }
    
        public static <T> Result<T> failed(Integer code, String msg, Throwable t) {
            return of(false, (Object)null, code, msg, t);
        }
    
        public Boolean getSuccess() {
            return this.success;
        }
    
        public Integer getCode() {
            return this.code;
        }
    
        public T getData() {
            return this.data;
        }
    
        public String getMessage() {
            return this.message;
        }
    
        public Throwable getThrowable() {
            return this.throwable;
        }
    
        public void setSuccess(Boolean success) {
            this.success = success;
        }
    
        public void setCode(Integer code) {
            this.code = code;
        }
    
        public void setData(T data) {
            this.data = data;
        }
    
        public void setMessage(String message) {
            this.message = message;
        }
    
        public void setThrowable(Throwable throwable) {
            this.throwable = throwable;
        }
    
        public boolean equals(Object o) {
            if (o == this) {
                return true;
            } else if (!(o instanceof Result)) {
                return false;
            } else {
                Result<?> other = (Result)o;
                if (!other.canEqual(this)) {
                    return false;
                } else {
                    label71: {
                        Object this$success = this.getSuccess();
                        Object other$success = other.getSuccess();
                        if (this$success == null) {
                            if (other$success == null) {
                                break label71;
                            }
                        } else if (this$success.equals(other$success)) {
                            break label71;
                        }
    
                        return false;
                    }
    
                    Object this$code = this.getCode();
                    Object other$code = other.getCode();
                    if (this$code == null) {
                        if (other$code != null) {
                            return false;
                        }
                    } else if (!this$code.equals(other$code)) {
                        return false;
                    }
    
                    label57: {
                        Object this$data = this.getData();
                        Object other$data = other.getData();
                        if (this$data == null) {
                            if (other$data == null) {
                                break label57;
                            }
                        } else if (this$data.equals(other$data)) {
                            break label57;
                        }
    
                        return false;
                    }
    
                    Object this$message = this.getMessage();
                    Object other$message = other.getMessage();
                    if (this$message == null) {
                        if (other$message != null) {
                            return false;
                        }
                    } else if (!this$message.equals(other$message)) {
                        return false;
                    }
    
                    Object this$throwable = this.getThrowable();
                    Object other$throwable = other.getThrowable();
                    if (this$throwable == null) {
                        if (other$throwable == null) {
                            return true;
                        }
                    } else if (this$throwable.equals(other$throwable)) {
                        return true;
                    }
    
                    return false;
                }
            }
        }
    
        protected boolean canEqual(Object other) {
            return other instanceof Result;
        }
    
        public int hashCode() {
            int PRIME = true;
            int result = 1;
            Object $success = this.getSuccess();
            int result = result * 59 + ($success == null ? 43 : $success.hashCode());
            Object $code = this.getCode();
            result = result * 59 + ($code == null ? 43 : $code.hashCode());
            Object $data = this.getData();
            result = result * 59 + ($data == null ? 43 : $data.hashCode());
            Object $message = this.getMessage();
            result = result * 59 + ($message == null ? 43 : $message.hashCode());
            Object $throwable = this.getThrowable();
            result = result * 59 + ($throwable == null ? 43 : $throwable.hashCode());
            return result;
        }
    
        public String toString() {
            return "Result(success=" + this.getSuccess() + ", code=" + this.getCode() + ", data=" + this.getData() + ", message=" + this.getMessage() + ", throwable=" + this.getThrowable() + ")";
        }
    
        public Result() {
        }
    
        public Result(Boolean success, Integer code, T data, String message, Throwable throwable) {
            this.success = success;
            this.code = code;
            this.data = data;
            this.message = message;
            this.throwable = throwable;
        }
    }
    

    相关文章

      网友评论

        本文标题:Spring Cloud Gateway 实现XSS、SQL注入

        本文链接:https://www.haomeiwen.com/subject/myevbktx.html