SpringBoot之RestTemplate在URL中轉(zhuǎn)義字符的問(wèn)題
RestTemplate在URL中轉(zhuǎn)義字符的問(wèn)題
相同代碼在不同SpringBoot版本中使用RestTemplate請(qǐng)求時(shí)遇到的問(wèn)題:
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("入?yún)?" + paramStr.toString()); RestTemplate restTemplate = new RestTemplate(); String jsonStr = restTemplate.getForObject(sendMessagesUrl + paramStr.toString(), String.class); logger.info("響應(yīng)值:" + jsonStr);
下面截圖為springboot1.5.3 RestTemplate request log
下面截圖為springboot2.1.7 RestTemplate request log
在這兩份log中可以看到user_sign對(duì)應(yīng)的value值中的 + 是特殊的字符,1.0版本的被轉(zhuǎn)義為: %2B,2.0版本沒(méi)有被轉(zhuǎn)義,最終2.0版本程序的RestTemplate請(qǐng)求第三方在簽名解碼時(shí)校驗(yàn)不通過(guò)。
1.嘗試與分析
根據(jù)上述信息我們可以圈定問(wèn)題的范圍,= 有被轉(zhuǎn)碼,說(shuō)明可能是在RestTemplate中url參數(shù)的構(gòu)建轉(zhuǎn)碼的方式上與httpClient有什么不通,嘗試進(jìn)行各類處理方法。
主要使用的方式有:
- UriComponent構(gòu)建uri(未解決)
- 構(gòu)建如下RestTemplateConfig(未解決)
public class RestTemplateConfig { @Bean RestTemplate restTemplate() { SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); requestFactory.setReadTimeout(40000); requestFactory.setConnectTimeout(40000); // 添加轉(zhuǎn)換器 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到這里時(shí)發(fā)現(xiàn)user_sign被轉(zhuǎn)義了但 + 沒(méi)有變。
這時(shí)URLDecoder.decode("/QaNSBls/U8ciXEWaCWtmeMK6+w%3D")會(huì)發(fā)現(xiàn) + 變成了 空格,導(dǎo)致第三方在簽名解碼時(shí)校驗(yàn)不通過(guò)。
再次通過(guò)比較會(huì)發(fā)現(xiàn)StringBoot2.0版本對(duì)應(yīng)的URL轉(zhuǎn)碼 + 不會(huì)被解析。
經(jīng)過(guò)多次百度有看到如下:
根據(jù) RFC 3986 加號(hào)等符號(hào)的確實(shí)可以出現(xiàn)在參數(shù)中的,而且不需要編碼,有問(wèn)題的在于服務(wù)端的解析沒(méi)有與時(shí)俱進(jìn)
3.解決問(wèn)題
最后解決一些這個(gè)問(wèn)題,當(dāng)使用RestTemplate發(fā)起請(qǐng)求,如果請(qǐng)求參數(shù)中有需要url編碼時(shí)
通過(guò)如下兩種方式解決:
1.不希望出現(xiàn)問(wèn)題的使用姿勢(shì)應(yīng)傳入U(xiǎn)RI對(duì)象而不是字符串,修改RestTemplate請(qǐng)求方法入?yún)⒌?code>String url 修改為 URI url
。
2.修改入?yún)⒕幋a格式URLEncoder.encode(user_sign, "UTF-8")
,然后在構(gòu)建RestTemplate時(shí),
Map<String, Object> param = new HashMap<String, Object>(); StringBuilder paramStr = new StringBuilder("?"); param.put("version","xxxxx"); param.put("time","xxxxx"); //轉(zhuǎn)化成md5生產(chǎn)密鑰 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);
小結(jié)
注意SpringBoot2.0版本的url參數(shù)編碼,默認(rèn)只會(huì)針對(duì) = 和 & 進(jìn)行處理;為了兼容我們一般的后端的url編解碼處理在需要編碼參數(shù)時(shí),
個(gè)人建議盡量不要使用Spring默認(rèn)的方式,不然接收到數(shù)據(jù)會(huì)和預(yù)期的不一致。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
mybatis中實(shí)現(xiàn)讓返回值與bean中字段相匹配
這篇文章主要介紹了mybatis中實(shí)現(xiàn)讓返回值與bean中字段相匹配,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-10-10Spring Boot實(shí)現(xiàn)數(shù)據(jù)訪問(wèn)計(jì)數(shù)器方案詳解
在Spring Boot項(xiàng)目中,有時(shí)需要數(shù)據(jù)訪問(wèn)計(jì)數(shù)器,怎么實(shí)現(xiàn)數(shù)據(jù)訪問(wèn)計(jì)數(shù)器呢?下面小編給大家?guī)?lái)了Spring Boot數(shù)據(jù)訪問(wèn)計(jì)數(shù)器的實(shí)現(xiàn)方案,需要的朋友參考下吧2021-08-08淺談Java并發(fā)中ReentrantLock鎖應(yīng)該怎么用
本文主要介紹了ava并發(fā)中ReentrantLock鎖的具體使用,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11JavaWeb請(qǐng)求轉(zhuǎn)發(fā)和請(qǐng)求包含實(shí)現(xiàn)過(guò)程解析
這篇文章主要介紹了JavaWeb請(qǐng)求轉(zhuǎn)發(fā)和請(qǐng)求包含實(shí)現(xiàn)過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02Mybatis-Plus使用p6spy對(duì)SQL性能進(jìn)行監(jiān)控的方法
這篇文章主要介紹了Mybatis-Plus使用p6spy對(duì)SQL性能進(jìn)行監(jiān)控的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12java poi導(dǎo)出excel時(shí)如何設(shè)置手動(dòng)換行
這篇文章主要介紹了java poi導(dǎo)出excel時(shí)如何設(shè)置手動(dòng)換行,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06淺談web服務(wù)器項(xiàng)目中request請(qǐng)求和response的相關(guān)響應(yīng)處理
這篇文章主要介紹了淺談web服務(wù)器項(xiàng)目中request請(qǐng)求和response的相關(guān)響應(yīng)處理,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07Spring Security+Spring Data Jpa如何進(jìn)行安全管理
這篇文章主要介紹了Spring Security+Spring Data Jpa如何進(jìn)行安全管理,幫助大家更好的理解和學(xué)習(xí)Spring Security框架,感興趣的朋友可以了解下2020-09-09