SpringCloud + SpringGateway 解决Get请求传参为特殊字符导致400无法通过网关转发的问题
场景:
公司以前的框架统一使用Post请求,传入参数为一个定义的公共类,类里面有个String类型的bean字段传入json字符串作为传参,emmm就想给他改成restful风格,在传入参数公共类无法改变的情况下,Get请求会传入特殊字符,导致400错误。例如:
1localhost:10001/verify/compreport/month?data={"compRefOwid":"1448487922485252098", "yhMonth":"2021-10"}
原因:
Tomcat的新版本中增加了一个新特性,就是严格按照 RFC 3986规范进行访问解析,而 RFC 3986规范定义了Url中只允许包含英文字母(a-zA-Z)、数字(0-9)、-_.~4个特殊字符以及所有保留字符(RFC3986中指定了以下字符为保留字符:! * ’ ( ) ; : @ & = + $ , / ? # [ ])。
解决方案选择:
- 前端请求时encode特殊字段(算了,不能因为自己的原因加大前端工作量)
- 改用post请求(emmm没有办法的办法,看着难受就是想要改了)
- 改Tomcat配置文件(由于是springboot项目,内嵌了tomcat,不方便修改,好吧就是我比较菜)
- 在后端代码层面解决这个问题
解决方法:
其他服务:由于使用的是内嵌的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配置的方式也无法生效。
1server:
2 tomcat:
3 relaxed-query-chars:
4 - "<"
5 - ">"
6 - "["
7 - "]"
8 - "{"
9 - "}"
随后参考了继承WebServerFactoryCustomizer
1import org.springframework.boot.context.properties.PropertyMapper;
2import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
3import org.springframework.boot.web.server.WebServerFactoryCustomizer;
4import org.springframework.stereotype.Component;
5
6/**
7 * Tomcat配置
8 * @author Atomicyo
9 * @version 1.0
10createTime: 2021-11-24T10:27:57+08:00
11updateTime: 2021-11-24T10:27:57+08:00
12 */
13@Component
14public class MyTomcatWebServerCustomize implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
15
16 private int maxParameterCount = 10000;
17
18 @Override
19 public void customize(TomcatServletWebServerFactory factory) {
20 PropertyMapper propertyMapper = PropertyMapper.get();
21 propertyMapper.from(this::getMaxParameterCount)
22 .when(v -> true)
23 .to(v -> customizerProperty(factory));
24 }
25
26 /**
27 * params特殊字符过滤
28 * @param factory
29 * @return void
30 * @author Atomicyo
31createTime: 2021-11-24T10:27:57+08:00
32updateTime: 2021-11-24T10:27:57+08:00
33 * @version 1.0
34 */
35 private void customizerProperty(TomcatServletWebServerFactory factory) {
36 factory.addConnectorCustomizers(
37 connector -> connector.setProperty("relaxedQueryChars", "[]{}"));
38 }
39
40 public void setMaxParameterCount(int maxParameterCount) {
41 this.maxParameterCount = maxParameterCount;
42 }
43
44 public int getMaxParameterCount() {
45 return maxParameterCount;
46 }
47}
48
网关模块:
由于spring gateway使用的是netty作为服务。所以修改tomcat配置的方式无法生效。参考Spring Cloud Gateway 和 Webflux 请求参数非法字符处理
1import io.netty.channel.ChannelHandlerContext;
2import io.netty.channel.ChannelInboundHandlerAdapter;
3import io.netty.handler.codec.http.HttpRequest;
4import lombok.extern.slf4j.Slf4j;
5import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
6import org.springframework.boot.web.server.WebServerFactoryCustomizer;
7import org.springframework.stereotype.Component;
8import reactor.netty.ConnectionObserver;
9import reactor.netty.NettyPipeline;
10
11import java.io.UnsupportedEncodingException;
12import java.net.URLEncoder;
13import java.util.ArrayList;
14import java.util.List;
15
16/**
17 * Netty编码配置
18 * @author Atomicyo
19 * @version 1.0
20createTime: 2021-11-24T10:27:57+08:00
21updateTime: 2021-11-24T10:27:57+08:00
22 */
23@Component
24@Slf4j
25public class EncodeQueryNettyWebServerCustomizer implements WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {
26
27 /**
28 * 需要encode的特殊字符
29 */
30 private final List<Character> charList = new ArrayList<Character>() {
31 {
32 this.add('{');
33 this.add('}');
34 this.add('[');
35 this.add(']');
36 }
37 };
38
39 @Override
40 public void customize(NettyReactiveWebServerFactory factory) {
41 factory.addServerCustomizers(httpServer ->
42 httpServer.observe((conn, state) -> {
43 if (state == ConnectionObserver.State.CONNECTED) {
44 conn.channel().pipeline().addAfter(NettyPipeline.HttpCodec, "", new QueryHandler());
45 }
46 }));
47 }
48
49
50 class QueryHandler extends ChannelInboundHandlerAdapter {
51
52 public QueryHandler() {
53 }
54
55 @Override
56 public void channelRead(ChannelHandlerContext ctx, Object msg) throws UnsupportedEncodingException {
57 if (msg instanceof HttpRequest) {
58 HttpRequest request = (HttpRequest) msg;
59 String url = request.uri();
60 // fix url
61 log.info("url: {}", url);
62 String[] split = url.split("\\?");
63 StringBuilder fixUrl = new StringBuilder(split[0]);
64 if (split.length > 1) {
65 fixUrl.append("?");
66 char[] chars = split[1].toCharArray();
67 for (char aChar : chars) {
68 if (charList.contains(aChar)) {
69 fixUrl.append(URLEncoder.encode(String.valueOf(aChar), "UTF-8"));
70 }else {
71 fixUrl.append(aChar);
72 }
73 }
74 }
75 log.info("fixUrl: {}", fixUrl);
76 request.setUri(fixUrl.toString());
77 }
78 ctx.fireChannelRead(msg);
79 }
80 }
81}
82