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);
        }
    }
}

验证:

相关推荐
evans在进步15 小时前
Spring Boot 工程化核心详解:Parent、Starter、热部署、事务与多数据源
spring boot·后端·python
卓怡学长15 小时前
w181springboot机场乘客服务系统
java·数据库·spring boot·spring·intellij-idea
阮胜昌15 小时前
MySQL 9.7.0 LTS 现已发布:扩展了社区功能,并为企业级应用增加了动态数据脱敏功能
数据库·mysql
宫水三叶的刷题日记15 小时前
馋猫外卖:AI 让字节式试错没有天花板
后端
码域空间16 小时前
锁住了检查,锁不住延迟——MySQL 读写分离架构下双重检查模式失效实录
数据库·mysql·架构
SomeB1oody16 小时前
【RustyML入门】7.4. 按需裁剪与模块化集成
开发语言·后端·机器学习·rust·教程
罗超驿16 小时前
SpringBoot 快速入门
java·spring boot·后端
卷无止境17 小时前
除了开发api,FastAPI其实也可以配合jinja2模板写页面
后端·python·fastapi
newerp17 小时前
Redis 操作与缓存策略
后端·程序员·go
newerp17 小时前
GORM ORM 基础
后端·程序员·go