先放结论:如果项目中使用Feign且使用了其中的RibbonClient,且requestUrl中含有'{'(不管是否UrlEncode),建议将Feign升级到9.5.0以上,原因在文章最后。
项目中需要使用Ribbon来实现软负载均衡等特性,由于Feign对其进行了封装,而且能简化调用代码,因此引入了Feign。但是在使用过程中发现一个问题,在使用RibbonClient的场景下,如果url的query里有'%7B'(url encode后的'{'),那在最终发送请求时,'%7B'会被转成'{',导致tomcat报400(参考CVE-2016-6816)。
发现问题后,开始着手排查,第一个想到的原因,是自己的使用姿势有问题。于是,找资料、看官方文档、看源码,一圈下来,没什么有用的资料和信息,姿势貌似也很标准,于是开启单步Debug模式,期望定位到出问题的点。
首先讲一下,阅读Feign源码,入口有2个,一个是根据自定义接口生成代理,入口为<T> T feign.Feign.Builder.target(Class<T> apiType, String url)
; 另一个入口是实际调用代理时的入口,为Object feign.ReflectiveFeign.FeignInvocationHandler.invoke(Object proxy, Method method, Object[] args)
。
祭出“二分Debug”大法,几个回合下来,定位到如下信息:
首先是调用链,请放大查看
LBClient.execute 中 request中url query是对的,request.toRequest()结果是错误的
进一步排查LBClient.RibbonRequest.toRequest() 中
返回的Request已经错了
问题出在新生成一个RequestTemplate中的append方法 第二步pullAnyQueriesOutOfUrl作用是将url中的query字段去除,但是其方法内实现有问题
RequestTemplate.pullAnyQueriesOutOfUrl方法 前半部分是将query所有字段解码并解析出来,没什么问题。 后半部分是重新编码并拼接,问题出在这个方法复用了 query(key, values)
这个方法,该方法的目的是生成编码后的query,但是里边有一块逻辑是有问题的,继续看代码
罪魁祸首是encodeIfNotVariable中硬编码,对"{"开头的字段都不会编码。
这段代码本身没什么问题,Feign在通过自定义接口生成代理时,占位符是'{userName}'这种格式,因此需要排除'{'。 问题出在,在实际调用时,会用实际参数替换占位符,替换后的query是需要对'{'进行编码的,也就是不能复用上述方法。这明显是一个bug,既然已经找到问题所在,只要修复就好了,修复后顺便给Feign提交一个pull request (#555)。
该pull request已合入9.5.0版本,因此有被同样问题困扰的同学,只需将版本升至9.5.0
及以上就可以了。
最后附9.5.0
的RibbonRequest.toRequest()
文中如有表述不清晰或有误的地方,欢迎指正。
本文来自网易实践者社区,经作者祝剑峰授权发布。