Gateway网关在url参数带有特殊字符的情况下转发失败(响应400)

本文主要分享了,SpringCloud Gateway网关在url参数带有空格或者特殊字符的情况下,转发失败导致响应错误码400的解决方案。

响应400错误码的2种场景:

1.参数带空格,Gateway会误认为该空格是切割符,如?phone= 135****6862&type=44,不能正常解析协议,直接异常

2.参数带特殊字符,如?phone=|135****6862&type=44,能正常解析协议,但转发后异常

一、问题分析

1.可用性<99的问题接口:

质量组反馈的可用性文档中,显示错误码(4xx)特别多,如下图

2.排查:

根据错误码匹配应用日志,未发现异常日志

根据错误码匹配Gateway网关日志,未发现异常日志

根据错误码匹配nginx日志,发现异常日志(基本都是响应400错误码)

json 复制代码
{
	"clientip": "100.xxx.xxx.250",
	"timestamp": "2024-01-03T00:52:10+08:00",
	"request_method": "POST",
	"response": "400",
	"byte": "131",
	"uri": "/api/xxx/xxx/smsCode",
	"param": "phone= 135****6862&type=44",
	"referrer": "-",
	"agent": "Mozilla/5.0 (Linux; U; Android 4.4.1; zh-cn; R815T Build/JOP40D) AppleWebKit/533.1 (KHTML, like Gecko)Version/4.0 MQQBrowser/4.5 Mobile Safari/533.1",
	"httpx_for": "221.xx.xx.60",
	"http_host": "domain.com",
	"upstream_addr": "172.xxx.xxx.123:8280",
	"request_time": "0.011",
	"upstream_response_time": "0.010",
	"ssl_protocol": "-"
}

3.分析:

是由于请求参数带了空格(phone= 135****6862&type=44)导致(该空格非前端正常传参误操作,是恶意请求),初步怀疑是nginx转发问题,因为网关未找到任何日志。经运维同事协助排查,发现是Gateway网关响应的400错误码,没有日志是因为日志级别没有设置到debug,所以未打印

4.排查与复现:

问题复现测试用例

断点排查发现经过代码HttpObjectDecoder.splitInitialLine后将sb内容切割为一个3个元素的数组,切割规则中空格就是其中一个切割符。

注意:这里不能使用hutool http工具请求,因为会对url进行urlencode,无法复现效果

sb样本为:

GET http://localhost:8280/api/xxx/xxx/smsCode?phone= 135****6862 HTTP/1.1

数组样本为:

["GET","http://localhost:8280/api/xxx/xxx/smsCode?phone=","135****6862 HTTP/1.1"]

后续代码中取第三个元素'135****6862 HTTP/1.1'作为http协议转换导致异常,所以响应400

二、解决方案

1.思路:

想办法改写HttpObjectDecoder.splitInitialLine的逻辑,上述情况数组样本为:

["GET","http://localhost:8280/api/xxx/xxx/smsCode?phone=135****6862","HTTP/1.1"]

2.实现:

经网络资料查询,可以通过以下代码在Netty ChannelPipeline中添加ChannelHandler

经源码分析,ChannelPipeline会有默认的http处理器NettyPipeline.HttpCodec(其实现类为HttpServerCodec),而HttpObjectDecoder.splitInitialLine的逻辑就是通过HttpServerCodec实现类来调用的

所以,想办法替换NettyPipeline.HttpCodec的实现类即可

复制框架源码HttpObjectDecoder、HttpRequestDecoder、HttpServerCodec至项目目录中(重命名加前缀Custom),如下图

将这3个类(Custom*)中用到HttpObjectDecoder、HttpRequestDecoder、HttpServerCodec的地方,全部修改为加Custom前缀的类,如

修改CustomHttpObjectDecoder.splitInitialLine的逻辑

核心思想:将sb按切割符切割出所有元素,数组组装逻辑改为取第一个、取最后一个、中间部分拼接在一起

sb样本为:

GET http://localhost:8280/api/xxx/xxx/smsCode?phone= 135****6862 HTTP/1.1

list样本为:

["GET","http://localhost:8280/api/xxx/xxx/smsCode?phone=","135****6862","HTTP/1.1"]

数组样本为:

["GET","http://localhost:8280/api/xxx/xxx/smsCode?phone=135****6862","HTTP/1.1"]

保险起见(非必要逻辑):

1)在异常情况下执行旧逻辑

2)增加开关控制(可通过apollo动态修改)

3.url带特殊字符处理:

某些情况下,参数带特殊字符,如下,Gateway也会响应400

http://localhost:8280/api/xxx/xxx/smsCode?phone=|135****6862

http://localhost:8280/api/xxx/xxx/smsCode?phone=?135****6862

解决方案是增加UrlParamHandler,获取url对其参数做urlencode

注意:hutool工具的URLUtil.encodeQuery会对一些RSA加密的+转换为空格,需要用URLUtil.encodeAll或者java原生java.net.URLEncoder,这2个又会把所有的路径参数、=、?之类的也转换,所以下面写了个复杂的逻辑去获取参数值,仅参数值做urlencode

怎么样?如果你觉得有用的话,还不快快收藏起来!!!

附:涉及的代码目录

github: github.com/897665787/s...

gitee:gitee.com/jq_di/sprin...

js 复制代码
springcloud-template
└── template-gateway
     └── netty -- 日志配置
          └── CustomHttpObjectDecoder -- 复制于源码
          └── CustomHttpRequestDecoder -- 复制于源码
          └── CustomHttpServerCodec -- 复制于源码
          └── NettyWebServerCustomizer -- 配置初始化
          └── UrlParamHandler -- 使用urlencode替换特殊字符
相关推荐
潘多编程15 分钟前
Spring Boot微服务架构设计与实战
spring boot·后端·微服务
2402_8575893620 分钟前
新闻推荐系统:Spring Boot框架详解
java·spring boot·后端
2401_8576226622 分钟前
新闻推荐系统:Spring Boot的可扩展性
java·spring boot·后端
Amagi.2 小时前
Spring中Bean的作用域
java·后端·spring
2402_857589362 小时前
Spring Boot新闻推荐系统设计与实现
java·spring boot·后端
J老熊2 小时前
Spring Cloud Netflix Eureka 注册中心讲解和案例示范
java·后端·spring·spring cloud·面试·eureka·系统架构
Benaso2 小时前
Rust 快速入门(一)
开发语言·后端·rust
sco52822 小时前
SpringBoot 集成 Ehcache 实现本地缓存
java·spring boot·后端
我爱学Python!2 小时前
面试问我LLM中的RAG,秒过!!!
人工智能·面试·llm·prompt·ai大模型·rag·大模型应用
OLDERHARD3 小时前
Java - LeetCode面试经典150题 - 矩阵 (四)
java·leetcode·面试