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错误。例如:

apl 复制代码
localhost:10001/verify/compreport/month?data={"compRefOwid":"1448487922485252098", "yhMonth":"2021-10"}

原因:

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配置的方式也无法生效。

yaml 复制代码
server:
  tomcat:
    relaxed-query-chars:
      - "<"
      - ">"
      - "["
      - "]"
      - "{"
      - "}"

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

SpringBoot2.0.0新版本内嵌Tomcat配置

java 复制代码
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;
    }
}

网关模块:

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

java 复制代码
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);
        }
    }
}

验证:

相关推荐
爱勇宝12 小时前
第 1 章:别把“需求文档”当成真正的需求
前端·后端·程序员
白帽小丑13 小时前
# 一次 MySQL DELETE 误操作的数据恢复尝试实录
数据库·mysql
Dxy123931021613 小时前
MySQL索引完整教程:创建、查看、修改、删除与日常管理
前端·javascript·mysql
至乐活着16 小时前
MySQL索引底层原理与查询优化实战:从B+树到执行计划
mysql·b+树·查询优化·sql性能·索引原理
IT_陈寒17 小时前
闭包陷阱让我加了两天班,JavaScript你真行
前端·人工智能·后端
易协同低代码18 小时前
通达OA核心类库TD类深度解析
后端
Gopher_HBo18 小时前
Go语言学习笔记(十八)Gin处理Session
后端
谭光志19 小时前
工具塞满上下文窗口怎么办?深度拆解 AI Agent Tool Search 按需加载实现原理
前端·后端·ai编程
她说..19 小时前
Java 默认值设置方式
java·开发语言·后端·springboot
foggyprojects19 小时前
从0开始,一句话启动AI DataAgent
后端·数据分析·ai编程