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


原文鏈接:https://blog.csdn.net/lance_lan/article/details/103885177

前言

網上有很多方式獲取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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

方式二

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

方案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(); 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

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

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

獲取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); } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
      routes: - id: info-service uri: lb://info-service predicates: - Path=/info/test/** - name: GatewayReadBodyPredicate args: inClass: java.lang.String 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

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<?, ?>" 
  • 1
  • 2
  • 3

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

方式三(有效方案)

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

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

主要涉及幾個內部類

SynchronossPartHttpMessageReader

SynchronossFormFieldPart(請求參數類型) SynchronossFilePart(文件類型) 
  • 1
  • 2
  • 3
  • 4
  • 5

關鍵性代碼

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); }); 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

獲取轉換后的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); }); } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

完整的驗簽代碼:

// 保存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 lance_lan_2016@163.com * @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); }); }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM