最近在開發一個基於 JWT(JSON Web Token)的身份驗證功能時,我遇到了一個讓人頭疼的問題:解碼後的 JWT 中的中文字符顯示為亂碼。經過一番折騰,終於找到了解決方案。
問題背景
JWT 是一種用於在網絡應用之間安全傳遞信息的開放標準(RFC 7519)。它由三部分組成:
- Header(頭部):描述簽名算法和令牌類型。
- Payload(載荷):包含實際的用户信息(如用户 ID、角色、權限等)。
- Signature(簽名):用於驗證令牌的完整性。
在我的項目中,JWT 的 Payload 部分包含了一些中文字符(如公司名稱)。然而,解碼後這些中文字符卻變成了亂碼,例如:
"X-Access-Control-Header-Company-Name": "æ¨ªæ¸ æºçµç§ææéå
¬"
問題分析
1. Base64Url 編碼問題
JWT 的 Header 和 Payload 部分是使用 Base64Url 編碼的。Base64Url 是 Base64 的一種變體,主要區別在於:
- 使用
-和_替代了 Base64 中的+和/。 - 去掉末尾的
=填充符。
2. atob 函數的侷限性
JavaScript 的 atob 函數用於解碼 Base64 字符串,但它只能處理 ASCII 字符。如果字符串中包含非 ASCII 字符(如中文),解碼結果就會變成亂碼。
3. UTF-8 編碼問題
中文字符在 JWT 中是使用 UTF-8 編碼的,而 atob 並不支持 UTF-8 解碼,因此需要額外的處理。
解決方案
為了解決這個問題,我對 decodeJWT 方法進行了改進,主要步驟如下:
1. Base64Url 解碼
將 Base64Url 轉換為標準的 Base64,並補充末尾的 = 填充符:
let base64 = str.replace(/-/g, '+').replace(/_/g, '/');
const padLength = 4 - (base64.length % 4);
if (padLength < 4) {
base64 += '='.repeat(padLength);
}
2. UTF-8 解碼
將解碼後的 Base64 字符串轉換為 UTF-8 編碼的字符串:
const utf8Str = decodeURIComponent(
atob(base64)
.split('')
.map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
.join('')
);
3. 完整代碼
以下是改進後的 decodeJWT 方法:
function decodeJWT(jwt) {
const parts = jwt.split('.');
if (parts.length !== 3) {
throw new Error('Invalid JWT');
}
const base64UrlDecode = (str) => {
let base64 = str.replace(/-/g, '+').replace(/_/g, '/');
const padLength = 4 - (base64.length % 4);
if (padLength < 4) {
base64 += '='.repeat(padLength);
}
return decodeURIComponent(
atob(base64)
.split('')
.map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
.join('')
);
};
const header = JSON.parse(base64UrlDecode(parts[0]));
const payload = JSON.parse(base64UrlDecode(parts[1]));
return [header, payload, parts[2]];
}
測試與驗證
使用改進後的方法解碼 JWT,結果如下:
輸入 JWT
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJYLUFjY2Vzcy1Db250cm9sLUhlYWRlci1Db21wYW55LU5hbWUiOiLmqKrmuKDmnLrnlLXnp5HmioDmnInpmZDlhawiLCJyb2xlcyI6WyJST0xFX01BTkFHRVIiXSwiYXV0aG9yaXRpZXMiOlsiUFJPSkVDVF9ERUxFVEUiLCJQUk9KRUNUX1ZJRVciLCJQUk9KRUNUX0NSRUFURSIsIlBST0pFQ1RfSU5BQ1RJVkUiLCJQUk9KRUNUX0FDVElWRSIsIlBST0pFQ1RfRURJVCIsIlBST0pFQ1RfQVNTSUdOIiwiU1RBVElPTl9WSUVXIiwiU1RBVElPTl9DUkVBVEUiLCJTVEFUSU9OX0lOQUNUSVZFIiwiU1RBVElPTl9BQ1RJVkUiLCJTVEFUSU9OX0VESVQiLCJERVZJQ0VfVFlQRV9WSUVXIiwiREVWSUNFX1RZUEVfQ1JFQVRFIiwiREVWSUNFX1RZUEVfREVMRVRFIiwiREVWSUNFX1RZUEVfRURJVCIsIkRFVklDRV9WSUVXIiwiREVWSUNFX0NSRUFURSIsIkRFVklDRV9JTkFDVElWRSIsIkRFVklDRV9BQ1RJVkUiLCJERVZJQ0VfRURJVCIsIkZJTEVfVVBMT0FEIiwiRklMRV9WSUVXIiwiVElDS0VUX1ZJRVciLCJUSUNLRVRfQ1JFQVRFIiwiVElDS0VUX1VQREFURSJdLCJzdWIiOiIxMjM0NTY3OCIsImV4cCI6MTc0MjMwNzQwNn0.R6cEpWzIquvwyBcOVYMtatMoVSj-0MuhDJ6Q1qLzenM
輸出結果
Header: {
"alg": "HS256",
"typ": "JWT"
}
Payload: {
"X-Access-Control-Header-Company-Name": "某某公司",
"roles": ["ROLE_MANAGER"],
"authorities": [
"PROJECT_DELETE",
"PROJECT_VIEW",
"PROJECT_CREATE",
"PROJECT_INACTIVE",
"PROJECT_ACTIVE",
"PROJECT_EDIT",
"PROJECT_ASSIGN",
"STATION_VIEW",
"STATION_CREATE",
"STATION_INACTIVE",
"STATION_ACTIVE",
"STATION_EDIT",
"DEVICE_TYPE_VIEW",
"DEVICE_TYPE_CREATE",
"DEVICE_TYPE_DELETE",
"DEVICE_TYPE_EDIT",
"DEVICE_VIEW",
"DEVICE_CREATE",
"DEVICE_INACTIVE",
"DEVICE_ACTIVE",
"DEVICE_EDIT",
"FILE_UPLOAD",
"FILE_VIEW",
"TICKET_VIEW",
"TICKET_CREATE",
"TICKET_UPDATE"
],
"sub": "12345678",
"exp": 1742307406
}
Signature: "R6cEpWzIquvwyBcOVYMtatMoVSj-0MuhDJ6Q1qLzenM"
您好,我是肥晨。 歡迎關注我獲取前端學習資源,日常分享技術變革,生存法則;行業內幕,洞察先機。