• 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);
                });
    }
  • 相关阅读:
    2017-2018-1 20155330 《信息安全系统设计基础》第11周学习总结
    2017-2018-1 20155321 20155330 《信息安全技术》 实验四 木马及远程控制技术
    2017-2018-1 20155330 《信息安全系统设计基础》第10周课堂测试&课下作业
    2017-2018-1 20155330 《信息安全系统设计基础》第9周学习总结
    2017-2018-1 20155321 20155330 《信息安全系统设计基础》实验三——实时系统
    2017-2018-1 20155330 《信息安全系统设计基础》加分项目--实现mypwd
    Web基础
    附录2
    C++中cout.setf()函数
    C++ 输入输出运算符重载
  • 原文地址:https://www.cnblogs.com/fswhq/p/14075071.html
Copyright © 2020-2023  润新知