Spring Cloud Gateway 实现XSS、SQL注入拦截

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;
    }
}
最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,992评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,212评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,535评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,197评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,310评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,383评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,409评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,191评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,621评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,910评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,084评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,763评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,403评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,083评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,318评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,946评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,967评论 2 351

推荐阅读更多精彩内容