• SpringCloud + SpringGateway 解决Get请求传参为特殊字符导致400无法通过网关转发的问题



    title: “SpringCloud + SpringGateway 解决Get请求传参为特殊字符导致400无法通过网关转发的问题”
    createTime: 2021-11-24T10:27:57+08:00
    updateTime: 2021-11-24T10:27:57+08:00
    draft: false
    author: “Atomicyo”
    tags: [“tomcat”]
    categories: [“java”]
    description: “SpringCloud + SpringGateway 解决Get请求传参为特殊字符导致400无法通过网关转发的问题”


    SpringCloud + SpringGateway 解决Get请求传参为特殊字符导致400无法通过网关转发的问题

    场景:

    公司以前的框架统一使用Post请求,传入参数为一个定义的公共类,类里面有个String类型的bean字段传入json字符串作为传参,emmm就想给他改成restful风格,在传入参数公共类无法改变的情况下,Get请求会传入特殊字符,导致400错误。例如:

    localhost:10001/verify/compreport/month?data={"compRefOwid":"1448487922485252098", "yhMonth":"2021-10"}
    
    • 1

    原因:

    Tomcat的新版本中增加了一个新特性,就是严格按照 RFC 3986规范进行访问解析,而 RFC 3986规范定义了Url中只允许包含英文字母(a-zA-Z)、数字(0-9)、-_.~4个特殊字符以及所有保留字符(RFC3986中指定了以下字符为保留字符:! * ’ ( ) ; : @ & = + $ , / ? # [ ])。

    解决方案选择:

    1. 前端请求时encode特殊字段(算了,不能因为自己的原因加大前端工作量)
    2. 改用post请求(emmm没有办法的办法,看着难受就是想要改了)
    3. 改Tomcat配置文件(由于是springboot项目,内嵌了tomcat,不方便修改,好吧就是我比较菜)
    4. 在后端代码层面解决这个问题

    解决方法:

    其他服务:由于使用的是内嵌的tomcat,网上常见的
    解决spring boot请求包含非法字符问题 The valid characters are defined in RFC 7230 and RFC 3986 错误
    配置TomcatServletWebServerFactory的方式使用时会导致两个TomcatServletWebServerFactory使springboot项目报错Unable to start ServletWebServerApplicationContext due to multiple ServletWebServerFactory beans而无法启动。而使用yml配置的方式也无法生效。

    server:
      tomcat:
        relaxed-query-chars:
          - "<"
          - ">"
          - "["
          - "]"
          - "{"
          - "}"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    随后参考了继承WebServerFactoryCustomizer的方式来修改Tomcat配置

    SpringBoot2.0.0新版本内嵌Tomcat配置

    import org.springframework.boot.context.properties.PropertyMapper;
    import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
    import org.springframework.boot.web.server.WebServerFactoryCustomizer;
    import org.springframework.stereotype.Component;
    
    /**
     * Tomcat配置
     * @author Atomicyo
     * @version 1.0
    createTime: 2021-11-24T10:27:57+08:00
    updateTime: 2021-11-24T10:27:57+08:00
     */
    @Component
    public class MyTomcatWebServerCustomize implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
    
        private int maxParameterCount = 10000;
    
        @Override
        public void customize(TomcatServletWebServerFactory factory) {
            PropertyMapper propertyMapper = PropertyMapper.get();
            propertyMapper.from(this::getMaxParameterCount)
                    .when(v -> true)
                    .to(v -> customizerProperty(factory));
        }
    
        /**
         * params特殊字符过滤
         * @param factory
         * @return void
         * @author Atomicyo
    createTime: 2021-11-24T10:27:57+08:00
    updateTime: 2021-11-24T10:27:57+08:00
         * @version 1.0
         */
        private void customizerProperty(TomcatServletWebServerFactory factory) {
            factory.addConnectorCustomizers(
                    connector -> connector.setProperty("relaxedQueryChars", "[]{}"));
        }
    
        public void setMaxParameterCount(int maxParameterCount) {
            this.maxParameterCount = maxParameterCount;
        }
    
        public int getMaxParameterCount() {
            return maxParameterCount;
        }
    }
    
    
    • 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

    网关模块:

    由于spring gateway使用的是netty作为服务。所以修改tomcat配置的方式无法生效。参考Spring Cloud Gateway 和 Webflux 请求参数非法字符处理

    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.ChannelInboundHandlerAdapter;
    import io.netty.handler.codec.http.HttpRequest;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
    import org.springframework.boot.web.server.WebServerFactoryCustomizer;
    import org.springframework.stereotype.Component;
    import reactor.netty.ConnectionObserver;
    import reactor.netty.NettyPipeline;
    
    import java.io.UnsupportedEncodingException;
    import java.net.URLEncoder;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * Netty编码配置
     * @author Atomicyo
     * @version 1.0
    createTime: 2021-11-24T10:27:57+08:00
    updateTime: 2021-11-24T10:27:57+08:00
     */
    @Component
    @Slf4j
    public class EncodeQueryNettyWebServerCustomizer implements WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {
    
        /**
         * 需要encode的特殊字符
         */
        private final List<Character> charList = new ArrayList<Character>() {
            {
                this.add('{');
                this.add('}');
                this.add('[');
                this.add(']');
            }
        };
    
        @Override
        public void customize(NettyReactiveWebServerFactory factory) {
            factory.addServerCustomizers(httpServer ->
                    httpServer.observe((conn, state) -> {
                        if (state == ConnectionObserver.State.CONNECTED) {
                            conn.channel().pipeline().addAfter(NettyPipeline.HttpCodec, "",  new QueryHandler());
                        }
                    }));
        }
    
    
        class QueryHandler extends ChannelInboundHandlerAdapter {
    
            public QueryHandler() {
            }
    
            @Override
            public void channelRead(ChannelHandlerContext ctx, Object msg) throws UnsupportedEncodingException {
                if (msg instanceof HttpRequest) {
                    HttpRequest request = (HttpRequest) msg;
                    String url = request.uri();
                    // fix url
                    log.info("url: {}", url);
                    String[] split = url.split("\\?");
                    StringBuilder fixUrl = new StringBuilder(split[0]);
                    if (split.length > 1) {
                        fixUrl.append("?");
                        char[] chars = split[1].toCharArray();
                        for (char aChar : chars) {
                            if (charList.contains(aChar)) {
                                fixUrl.append(URLEncoder.encode(String.valueOf(aChar), "UTF-8"));
                            }else {
                                fixUrl.append(aChar);
                            }
                        }
                    }
                    log.info("fixUrl: {}", fixUrl);
                    request.setUri(fixUrl.toString());
                }
                ctx.fireChannelRead(msg);
            }
        }
    }
    
    
    • 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
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82

    验证:

    在这里插入图片描述

  • 相关阅读:
    手把手教你搭建ELK-新手必看-第一章:搭建ES
    统计学-双变量相关分析-相关系数、相关比、克莱姆相关系数
    Servlet属性、监听者和会话
    AIE有机荧光探针/荧光高分子纳米微球AIE-PEN FPNs/AIE有机荧光分子带电荷微球
    10w字的Java面试手册,基本涵盖了所有会问的
    linux添加一条到中间路由器的路由
    技能大赛试题剖析:文件上传渗透测试
    基于ssm的大学生实习平台系统(前端+后端)
    华为云云耀云服务器L实例评测|基于云耀云服务器在Docker上部署nginx服务
    Java 进阶集合和数据结构
  • 原文地址:https://blog.csdn.net/ZHUXIUQINGIT/article/details/133365799