Spring Boot - RestTemplate在URL中转义字符问题
相同代码在不同SpringBoot版本中使用RestTemplate请求时遇到的问题:
Map<String, Object> param = new HashMap<String, Object>(); param.put("version","2.0.0"); String userSing = sign(param); param.put("user_sign", userSing); logger.info("签名:" + userSing); StringBuilder paramStr = new StringBuilder("?"); for(Map.Entry<String, Object> entry : param.entrySet()){ paramStr.append(entry.getKey()).append("=") .append(entry.getValue() == null ? "" : String.valueOf(entry.getValue())) .append("&"); } paramStr.deleteCharAt(paramStr.length() - 1); logger.info("入参:" + paramStr.toString()); RestTemplate restTemplate = new RestTemplate(); String jsonStr = restTemplate.getForObject(sendMessagesUrl + paramStr.toString(), String.class); logger.info("响应值:" + jsonStr);
下面截图为springboot1.5.3 RestTemplate request log
下面截图为springboot2.1.7 RestTemplate request log
在这两份log中可以看到user_sign对应的value值中的 + 是特殊的字符,1.0版本的被转义为: %2B,2.0版本没有被转义,最终2.0版本程序的RestTemplate请求第三方在签名解码时校验不通过。
1.尝试与分析
根据上述信息我们可以圈定问题的范围,= 有被转码,说明可能是在RestTemplate中url参数的构建转码的方式上与httpClient有什么不通,尝试进行各类处理方法。
主要使用的方式有:
- UriComponent构建uri(未解决)
- 构建如下RestTemplateConfig(未解决)
public class RestTemplateConfig { @Bean RestTemplate restTemplate() { SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); requestFactory.setReadTimeout(40000); requestFactory.setConnectTimeout(40000); // 添加转换器 List<HttpMessageConverter<?>> messageConverters = new ArrayList<>(); messageConverters.add(new StringHttpMessageConverter(Charset.forName("UTF-8"))); messageConverters.add(new FormHttpMessageConverter()); messageConverters.add(new MappingJackson2HttpMessageConverter()); RestTemplate restTemplate = new RestTemplate(messageConverters); restTemplate.setRequestFactory(requestFactory); restTemplate.setErrorHandler(new DefaultResponseErrorHandler()); return restTemplate; } }
2.跟踪RestTemplate源码
debug到这里时发现user_sign被转义了但 + 没有变。
这时URLDecoder.decode("/QaNSBls/U8ciXEWaCWtmeMK6+w%3D")会发现 + 变成了 空格,导致第三方在签名解码时校验不通过。
再次通过比较会发现StringBoot2.0版本对应的URL转码 + 不会被解析。
经过多次百度有看到如下:
根据 RFC 3986 加号等符号的确实可以出现在参数中的,而且不需要编码,有问题的在于服务端的解析没有与时俱进
3.解决问题
最后解决一些这个问题,当使用RestTemplate发起请求,如果请求参数中有需要url编码时,通过如下两种方式解决:
- 不希望出现问题的使用姿势应传入URI对象而不是字符串,修改RestTemplate请求方法入参的
String url
修改为URI url
。 - 修改入参编码格式
URLEncoder.encode(user_sign, "UTF-8")
,然后在构建RestTemplate时,
Map<String, Object> param = new HashMap<String, Object>(); StringBuilder paramStr = new StringBuilder("?"); param.put("version","xxxxx"); param.put("time","xxxxx"); //转化成md5生产密钥 String userSing = sign(param); String userSign = URLEncoder.encode(userSing, "UTF-8"); param.put("user_sign", userSign); for(Map.Entry<String, Object> entry : param.entrySet()){ paramStr.append(entry.getKey()).append("=") .append(entry.getValue() == null ? "" : String.valueOf(entry.getValue())) .append("&"); } paramStr.deleteCharAt(paramStr.length() - 1); CloseableHttpClient httpClient = HttpClientUtils.acceptsUntrustedCertsHttpClient(); HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory); DefaultUriBuilderFactory uriFactory = new DefaultUriBuilderFactory(); uriFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY); restTemplate.setUriTemplateHandler(uriFactory); String smsJsonStr = restTemplate.getForObject(SMSURL + paramStr.toString(), String.class); Map<String, Object> map = GsonUtil.gsonToMap(smsJsonStr);
小结
注意SpringBoot2.0版本的url参数编码,默认只会针对 = 和 & 进行处理;为了兼容我们一般的后端的url编解码处理在需要编码参数时,个人建议尽量不要使用Spring默认的方式,不然接收到数据会和预期的不一致。
热门文章
- 「2月4日」2025年最新高速Clash/V2ray/SSR/Shadowrocket免费节点订阅链接地址分享
- 妙三多和猫三联价格是多少(妙三多和猫三联是疫苗的名字吗)
- 美容美体包括什么项目(美容美体包括什么项目好学吗)
- 「2月8日」2025年最新高速V2ray/Clash/Shadowrocket/SSR免费节点订阅链接地址分享
- 宠物医院服务项目价格表(宠物医院服务项目价格表最新)
- 动物疫苗官网查询系统网址 动物疫苗官网查询系统网址
- 动物医院诊疗范围有哪些内容(动物医院诊疗范围有哪些内容呢)
- 「3月18日」2025年最新高速SSR/Shadowrocket/V2ray/Clash免费节点订阅链接地址分享
- 「2月26日」2025年最新高速V2ray/Shadowrocket/Clash/SSR免费节点订阅链接地址分享
- Spring AOP—面向切面编程入门