Contents

Java 做 Base64 Url Encode

之前我在有關 url 空白字符(%20和+) | 程式狂想筆記有記過,URL 中的 + 很容易被當成空白處理。所以如果把一般 Base64 字串直接塞進 query string,就很有機會踩雷。

為什麼一般 Base64 不適合直接放 URL

標準 Base64 常見字元包含:

  1. +
  2. /
  3. =

這幾個字元放在 URL、路由參數、表單傳送時,都可能需要額外處理。尤其 + 在某些情境下會被解讀成空白,最後 server 端拿到的字串就跟原本不一樣了。

例如下面這個請求:

https://httpbin.org/get?test=123+123

https://i.imgur.com/0hajVfj.png

如果你原本以為 + 會原封不動傳過去,很容易就會被這種結果陰到。

解法:使用 URL Safe Base64

所謂 URL Safe Base64,核心就是把不適合出現在 URL 的字元換成較安全的版本。常見規則是:

  1. + 改成 -
  2. / 改成 _
  3. 視情況去掉尾端 = padding

這樣就更適合放在 query string、JWT 片段、下載 token 或短網址參數。

Java 內建寫法

如果是 Java 8 之後,其實可以先用 JDK 內建的 Base64:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import java.nio.charset.StandardCharsets;
import java.util.Base64;

String raw = "userId=123+456";
String encoded = Base64.getUrlEncoder()
	.withoutPadding()
	.encodeToString(raw.getBytes(StandardCharsets.UTF_8));

String decoded = new String(
	Base64.getUrlDecoder().decode(encoded),
	StandardCharsets.UTF_8
);

這種寫法的好處是少一個相依套件,而且語意很直接。

Apache Commons 寫法

如果專案本來就有用 Apache Commons Codec,也可以直接用:

1
2
String encoded = Base64.encodeBase64URLSafeString(data);
byte[] decoded = Base64.decodeBase64(encoded);

這也是我當時查到後先記下來的方式。對應程式碼可以參考:

commons-codec/Base64.java at ebebbd427d80639692b2a06e51cc2885a1543b18 · apache/commons-codec · GitHub

它有個不錯的地方是,URL Safe encode 出來的字串,仍然可以用 decodeBase64 做還原。

什麼時候特別需要注意

下面幾種場景,我會直接優先用 URL Safe Base64:

  1. 資料要放在 GET 參數。
  2. 資料要放在網址 path segment。
  3. 前後端之間會手動拼接 URL。
  4. token 會出現在瀏覽器網址列。

如果你的資料只是存在資料庫、寫檔案、放 JSON body,其實就不一定非要用 URL Safe 版本。

小結

這個問題本質上不是 Base64 壞掉,而是標準 Base64 的字元集合不一定適合拿來當 URL 參數。只要知道這點,以後看到網址中的 +/=,就會自然警覺是不是該改用 URL Safe 版本。