文章目录
- 问题背景
- 问题分析
- 1. **Base64Url 编码问题**
- 2. **`atob` 函数的局限性**
- 3. **UTF-8 编码问题**
- 解决方案
- 1. **Base64Url 解码**
- 2. **UTF-8 解码**
- 3. **完整代码**
- 测试与验证
- 输入 JWT
- 输出结果
最近在开发一个基于 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"
您好,我是肥晨。
欢迎关注我获取前端学习资源,日常分享技术变革,生存法则;行业内幕,洞察先机。