springcloud gateway GlobalFilter 簽名校驗,獲取Post請求體

springcloud gateway GlobalFilter 簽名校驗,獲取Post請求體

前言

網上有很多方式獲取Post請求內容,嘗試了好多種方式,都不是最佳的使用方式。
方式一

網上大多的解決方會有很多坑,網上說最大隻能1024B(點擊快速傳送),個人沒有采用

if ("POST".equals(method)) {
       //從請求裏獲取Post請求體
       String bodyStr = resolveBodyFromRequest(serverHttpRequest);
       URI uri = serverHttpRequest.getURI();
       ServerHttpRequest request = serverHttpRequest.mutate().uri(uri).build();
       DataBuffer bodyDataBuffer = stringBuffer(bodyStr);
       Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);

       request = new ServerHttpRequestDecorator(request) {
           @Override
           public Flux<DataBuffer> getBody() {
               return bodyFlux;
           }
       };
       //封裝request,傳給下一級
       return chain.filter(exchange.mutate().request(request).build());
} else if ("GET".equals(method)) {
       Map requestQueryParams = serverHttpRequest.getQueryParams();
       //TODO 得到Get請求的請求參數後,做你想做的事
    
       return chain.filter(exchange);
}

方式二

方法二,也是我一直想要採用的方式,但最終也放棄了。
方案1

選擇是使用代碼的形式配置路由,在路由裏面配置ReadBodyPredicate預言類。

RouteLocatorBuilder.Builder serviceProvider = builder.
                routes().route("info-service",
                    r -> r.readBody(String.class, requestBody -> {
                        log.info("requestBody is {}", requestBody);
                        return true;
                }).and().path("/info/test").
                        filters(f -> {
                            f.filter(requestFilter);
                            return f;
                        })
                        .uri("info-service"));
RouteLocator routeLocator = serviceProvider.build();

然後通過全局Filter中的 exchange的方式獲取屬性的形式獲取

String body = exchange.getAttribute("cachedRequestBodyObject");

獲取post請求的字符串形式以後呢,轉換也比較麻煩,如果不是form表單形式的post請求還比較好轉換,如果是form表單的形式,再加上"multipart/form-data"的形式,直接獲取的就是http請求過來的數據,沒有封裝。
方案2

個人比較傾向的方式,就是採用yml的配置形式。通過繼承ReadBodyPredicateFactory的形式,可以達到route編碼的形式

參考代碼地址:https://github.com/spring-cloud/spring-cloud-gateway/issues/1307

繼承ReadBodyPredicateFactory

@Component
public class GatewayReadBodyPredicate extends ReadBodyPredicateFactory {
    public static final String REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject";
    @Override
    public AsyncPredicate<ServerWebExchange> applyAsync(Config config) {
        config.setPredicate(t -> true);
        return super.applyAsync(config);
    }
}

      routes:
        - id: info-service  
          uri: lb://info-service
          predicates:
            - Path=/info/test/**
            - name: GatewayReadBodyPredicate
              args:
                inClass: java.lang.String

springcloud-gateway,github上給出的配置中,可以將配置信息中的參數的inClass設置爲org.springframework.util.MultiValueMap,但是在實際使用過程中,如果是文件和form表單一起提交的話,拋出異常。

github截圖
在這裏插入圖片描述
異常信息如下:

org.springframework.web.server.UnsupportedMediaTypeStatusException: 415 UNSUPPORTED_MEDIA_TYPE
"Content type 'multipart/form-data;boundary=-------------------------
-873485462073103209590464' not supported for bodyType=org.springframework.util.MultiValueMap<?, ?>"

 

個人沒有找到合適的類型來接收請求內容類型是**“multipart/form-data”**的java類型參數,最終放棄使用這種形式。
方式三(有效方案)

通過Filter的形式,轉換新的request請求,然後獲取body內容。
(參考地址找不見了,o(╥﹏╥)o)

代碼註釋很清楚,就不在多說.

主要涉及幾個內部類

SynchronossPartHttpMessageReader

SynchronossFormFieldPart(請求參數類型)

SynchronossFilePart(文件類型)

關鍵性代碼

DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> {
                DataBufferUtils.retain(dataBuffer);
                final Flux<DataBuffer> cachedFlux = Flux.defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));
                final ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
                    @Override
                    public Flux<DataBuffer> getBody() {
                        return cachedFlux;
                    }
                };
                final ServerWebExchange mutatedExchange = exchange.mutate().request(mutatedRequest).build();

                return cacheBody(mutatedExchange, chain, params);
            });

獲取轉換後的ServerWebExchange以後,分析解析其內容。

private Mono<Void> cacheBody(ServerWebExchange exchange, GatewayFilterChain chain, Map<String, String> params) {
        final HttpHeaders headers = exchange.getRequest().getHeaders();
        if (headers.getContentLength() == 0) {
            return chain.filter(exchange);
        }
        final ResolvableType resolvableType;
        if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(headers.getContentType())) {
            resolvableType = ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, Part.class);
            // 通過這裏,大家也能瞭解一些,爲什麼在上面預言類裏直接使用org.springframework.util.MultiValueMap不行,因爲還要傳入Part類型。
        } else {
            resolvableType = ResolvableType.forClass(String.class);
        }
        return MESSAGE_READERS.stream().filter(reader -> reader.canRead(resolvableType,
                exchange.getRequest().getHeaders().getContentType())).findFirst()
                .orElseThrow(() -> new IllegalStateException("no suitable HttpMessageReader.")).readMono(resolvableType,
                        exchange.getRequest(), Collections.emptyMap()).flatMap(resolvedBody -> {
                    if (resolvedBody instanceof MultiValueMap) {
                        @SuppressWarnings("rawtypes")
                        MultiValueMap<String, Object> map = (MultiValueMap) resolvedBody;
                        map.keySet().forEach(key -> {
//                            SynchronossPartHttpMessageReader
                            Object obj = map.get(key);
                            List<Object> list = (List<Object>) obj;
                            for (Object object : list) {
                                if (object.getClass().toString().equals("class org.springframework.http.codec.multipart.SynchronossPartHttpMessageReader$SynchronossFilePart")) {
                                // 過濾如果是SynchronossFilePart這個文件類型,就是傳入的文件參數,做簽名校驗的時候,我這裏沒有驗籤文件體
                                    continue;
                                }
                                // 通過反射的形式獲取這個類型SynchronossPartHttpMessageReader下面的私有類SynchronossFormFieldPart的參數值
                                Field[] fields = object.getClass().getDeclaredFields();
                                try {
                                    for (Field field : fields) {
                                        field.setAccessible(true);
                                        // 保存到傳入map中
                                        params.put(key, field.get(object) + "");
                                    }
                                } catch (IllegalAccessException e) {
                                    e.printStackTrace();
                                    LogUtils.info(e.getLocalizedMessage());
                                }
                            }
                        });
                    } else {
                        // post請求中,在請求地址中的參數,如果做鑑權,也要考慮到
                    }
                    // 驗籤或者其他操作
                    return chain.filter(exchange);
                });
    }

完整的驗籤代碼:

// 保存HttpMessageReader
private static final List<HttpMessageReader<?>> MESSAGE_READERS = HandlerStrategies.withDefaults().messageReaders();

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

    LogUtils.info("訪問地址:" + request.getURI().toString());

    // 請求參數上的url地址
    Map<String, String> params = new HashMap<>();
    request.getQueryParams().forEach((key, items) -> {
        params.put(key, items.get(0));
    });
    if ("GET".equals(request.getMethodValue())) {
        return this.checkSign(params, chain, exchange);
    } else if ("POST".equals(request.getMethodValue())) {
        return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> {
            DataBufferUtils.retain(dataBuffer);
            final Flux<DataBuffer> cachedFlux = Flux.defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));
            final ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
                @Override
                public Flux<DataBuffer> getBody() {
                    return cachedFlux;
                }
            };
            final ServerWebExchange mutatedExchange = exchange.mutate().request(mutatedRequest).build();

            return cacheBody(mutatedExchange, chain, params);
        });

    }
    return chain.filter(exchange);
}

/***
 * 驗證簽名
 * @author Lance [email protected]
 * @date 2020-01-07 09:57
 * @param params
 * @param chain
 * @param exchange
 * @return reactor.core.publisher.Mono<java.lang.Void>
 *
 * */
private Mono<Void> checkSign(Map<String, String> params, GatewayFilterChain chain,
                             ServerWebExchange exchange) {
    LogUtils.info("校驗參數集合:" + params);
    if (!MD5Sign.checkSign(appSecret, params)) {
        // 返回json格式
        JsonResponse jsonResponse = new JsonResponse();
        jsonResponse.errorAuth();

        exchange.getResponse().setStatusCode(HttpStatus.OK);
        exchange.getResponse().getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return exchange.getResponse().writeWith(Mono.just(exchange.getResponse().bufferFactory().wrap(JsonUtils.toString(jsonResponse).getBytes())));
    }
    return chain.filter(exchange);
}

@SuppressWarnings("unchecked")
private Mono<Void> cacheBody(ServerWebExchange exchange, GatewayFilterChain chain, Map<String, String> params) {
    final HttpHeaders headers = exchange.getRequest().getHeaders();
    if (headers.getContentLength() == 0) {
        return chain.filter(exchange);
    }
    final ResolvableType resolvableType;
    if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(headers.getContentType())) {
        resolvableType = ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, Part.class);
    } else {
        resolvableType = ResolvableType.forClass(String.class);
    }

    return MESSAGE_READERS.stream().filter(reader -> reader.canRead(resolvableType,
            exchange.getRequest().getHeaders().getContentType())).findFirst()
            .orElseThrow(() -> new IllegalStateException("no suitable HttpMessageReader.")).readMono(resolvableType,
                    exchange.getRequest(), Collections.emptyMap()).flatMap(resolvedBody -> {
                if (resolvedBody instanceof MultiValueMap) {
                    @SuppressWarnings("rawtypes")
                    MultiValueMap<String, Object> map = (MultiValueMap) resolvedBody;
                    map.keySet().forEach(key -> {
//                            SynchronossPartHttpMessageReader
                        Object obj = map.get(key);
                        List<Object> list = (List<Object>) obj;
                        for (Object object : list) {
                            if (object.getClass().toString().equals("class org.springframework.http.codec.multipart.SynchronossPartHttpMessageReader$SynchronossFilePart")) {
                                continue;
                            }
                            Field[] fields = object.getClass().getDeclaredFields();
                            try {
                                for (Field field : fields) {
                                    field.setAccessible(true);
                                    params.put(key, field.get(object) + "");
                                }
                            } catch (IllegalAccessException e) {
                                e.printStackTrace();
                                LogUtils.info(e.getLocalizedMessage());
                            }
                        }
                    });
                } else {
                    if (null != resolvedBody) {
                        String path = null;
                        try {
                            path = URLDecoder.decode(((Object) resolvedBody).toString(), "UTF-8");
                        } catch (UnsupportedEncodingException e) {
                            e.printStackTrace();
                            LogUtils.error(e.getLocalizedMessage());
                        }
                        if (null != path) {
                            String items[] = path.split("&");
                            for (String item: items) {
                                String subItems[] = item.split("=");
                                if (null != subItems && subItems.length == 2) {
                                    params.put(subItems[0], subItems[1]);
                                }
                            }
                        }
                    }

                }
                return this.checkSign(params, chain, exchange);
            });
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章