問題
一個 springboot 應用,包含如下 controller
@RestController
public class DemoController {@GetMapping("/get")public ResponseEntity<String> get(@RequestParam(value = "cid2") String cid2)
準備測試數據
String cid2 = "1;MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoJDsA9HldKEk8i/lbRxS8E13HlCBjg63CrDLF6ieQT+h7K9eRS5jH0VgYXLmWJLOnGLrH1324JaEYHbTmZgpVw==;eWxJY0hrdlBPYVMz;MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEpto5kN5HEx/OvVTV2ux08EvneYb+cNx4g9G7ajgH41gBCl7G5JkbaYA7Wf9DxOks3Syu/NJPwhjbOz32L+BfTA==;hcHB3S1D5hgfe5Ar9DUKqW3SeCrvAgeXzt7RldZTh/qWCouFW6NqEZfycxe9Tqi9ehemNc1cMaAtAsC2Ng34hZsEN/cdyqGRcRTbYSpEtdL3GVbyIF5S9hTYD9wcvUMMhsnFIfUMZD0=";
發起請求
String url = UriComponentsBuilder.fromHttpUrl("http://localhost:8080/get").queryParam("cid2", cid2).toUriString();
ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class);
發現問題,controller 接收到的數據為
1;MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoJDsA9HldKEk8i/lbRxS8E13HlCBjg63CrDLF6ieQT h7K9eRS5jH0VgYXLmWJLOnGLrH1324JaEYHbTmZgpVw==;eWxJY0hrdlBPYVMz;MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEpto5kN5HEx/OvVTV2ux08EvneYb cNx4g9G7ajgH41gBCl7G5JkbaYA7Wf9DxOks3Syu/NJPwhjbOz32L BfTA==;hcHB3S1D5hgfe5Ar9DUKqW3SeCrvAgeXzt7RldZTh/qWCouFW6NqEZfycxe9Tqi9ehemNc1cMaAtAsC2Ng34hZsEN/cdyqGRcRTbYSpEtdL3GVbyIF5S9hTYD9wcvUMMhsnFIfUMZD0=
對比發現其中的加號變成了空格,而我們希望傳遞到服務端的是依然是加號。
?
原因分析
根據 URL 編碼的規范,空格字符在 URL 查詢字符串中通常會被編碼為 +
。這意味著當發送一個 URL 請求并將 +
作為查詢參數時,瀏覽器和服務器會認為 +
是空格的替代符號。
舉個例子:
假設你傳遞的 URL 是:Example Domain,param=hello+world
中的 +
會被自動解碼為一個空格,最終得到的參數值就是 hello world
,而不是原始的 hello+world
。
解決方法
通過 URLEncoder
進行編碼
Java 的 URLEncoder
可以將特殊字符(如 ;
, /
, +
, =
, %
)轉義成 URL 編碼的格式。例如,空格被編碼為 +
,而其他特殊字符會轉換成以 %
開頭的編碼形式。
客戶端預先編碼,得到:
1%3BMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoJDsA9HldKEk8i%2FlbRxS8E13HlCBjg63CrDLF6ieQT%2Bh7K9eRS5jH0VgYXLmWJLOnGLrH1324JaEYHbTmZgpVw%3D%3D%3BeWxJY0hrdlBPYVMz%3BMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEpto5kN5HEx%2FOvVTV2ux08EvneYb%2BcNx4g9G7ajgH41gBCl7G5JkbaYA7Wf9DxOks3Syu%2FNJPwhjbOz32L%2BBfTA%3D%3D%3BhcHB3S1D5hgfe5Ar9DUKqW3SeCrvAgeXzt7RldZTh%2FqWCouFW6NqEZfycxe9Tqi9ehemNc1cMaAtAsC2Ng34hZsEN%2FcdyqGRcRTbYSpEtdL3GVbyIF5S9hTYD9wcvUMMhsnFIfUMZD0%3D
服務端接受到(觀察以下數據可以發現%
在傳輸之前再次被編碼為%25
):
1%253BMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoJDsA9HldKEk8i%252FlbRxS8E13HlCBjg63CrDLF6ieQT%252Bh7K9eRS5jH0VgYXLmWJLOnGLrH1324JaEYHbTmZgpVw%253D%253D%253BeWxJY0hrdlBPYVMz%253BMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEpto5kN5HEx%252FOvVTV2ux08EvneYb%252BcNx4g9G7ajgH41gBCl7G5JkbaYA7Wf9DxOks3Syu%252FNJPwhjbOz32L%252BBfTA%253D%253D%253BhcHB3S1D5hgfe5Ar9DUKqW3SeCrvAgeXzt7RldZTh%252FqWCouFW6NqEZfycxe9Tqi9ehemNc1cMaAtAsC2Ng34hZsEN%252FcdyqGRcRTbYSpEtdL3GVbyIF5S9hTYD9wcvUMMhsnFIfUMZD0%253D
服務端要對接收到的數據 decode 1 次才能得到客戶端預先編碼后的數據,decode 2 次才能獲得我們想要傳輸的帶特殊符號的數據。
?
POST 請求傳遞特殊字符
在 POST
請求中,數據被發送到請求體 body,而不是 URL 中,因此特殊字符不會被編碼,以下兩種方式都可以。
application/json
headers.setContentType(MediaType.APPLICATION_JSON);
Map<String, String> body = new HashMap<>();
body.put("cid2", cid2);RequestEntity<Map> requestEntity = new RequestEntity<>(body,headers,HttpMethod.POST,URI.create("http://localhost:8080/post")
);ResponseEntity<String> exchange = restTemplate.exchange(requestEntity, String.class);
application/x-www-form-urlencoded
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
formData.add("param", cid2);RequestEntity<MultiValueMap<String, String>> requestEntity = new RequestEntity<>(formData,headers,HttpMethod.POST,URI.create("http://localhost:8080/submit")
);ResponseEntity<String> exchange = restTemplate.exchange(requestEntity, String.class);
?