一文了解 JWT Token
在實際開發中,使用令牌認證,還有其他很多好處。掌握令牌認證的原理和實現方法,是 Go 語言開發者,必備的核心技能之一。
由于 miniblog 使用 JWT Token 進行身份認證,為了降低學習難度并為后續代碼實現奠定基礎,本節課將介紹 JWT 的核心內容。
1. JWT 認證流程
學習 JWT 的最佳方式是通過其認證流程理解其原理。認證流程如下圖所示:
上圖展示了 JWT 的認證流程,具體流程如下:
- 客戶端(通常是前端)通過用戶名和密碼進行登錄;
- 服務端收到請求后會驗證用戶名和密碼,若與數據庫記錄不一致,則認證失敗,若一致,則認證通過。認證通過后,服務端會簽發一個具有有效期的 Token 并返回給客戶端;
- 客戶端接收到 Token 后會將其緩存,例如存儲在瀏覽器的 Cookie 或本地存儲中,方便下次調用時使用;
- 客戶端在之后的每次 API 請求中攜帶緩存的 Token;
- 服務端接收到請求后會驗證請求中攜帶的 Token,驗證通過后繼續處理業務邏輯并返回數據;
- 如果 Token 快過期,前端會調用 Token 刷新接口續期 Token,避免用戶再次登錄。之后,會使用續期后的 Token 發送 API 請求。
提示:Go 項目開發中,Token 有效期通常設置為 2 小時。
2. JWT Token 格式
在 JWT 中,Token 由 Header、Payload、Signature 三部分組成,中間用英文點號(.)隔開,并使用 Base64 編碼。JWT Token 示例如下:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MzkwNzgwMDUsImlhdCI6MTczNTQ3ODAwNSwibmJmIjoxNzM1NDc4MDA1LCJ4LXVzZXItaWQiOiJ1c2VyLXc2aXJrZyJ9.GromRG7kK90UfU_Q5iOSHs_xE-zSk0e0HLHqJQUjYMU
(1) Header 介紹
JWT Token 的 Header 中包含兩部分信息:Token 的類型和 Token 所使用的加密算法。JWT Header 示例如下:
{
"typ": "JWT",
"alg": "HS256"
}
上述示例表明,Token 類型是 JWT,加密算法為 HS256(alg 支持多種加密算法)。
(2) Payload 載荷介紹
Payload 中攜帶了 Token 的具體內容,其中包含一些標準字段,當然也可以添加額外字段以表達更豐富的信息。這些信息可以用于更復雜的處理場景,例如記錄請求的用戶 ID、用戶名等。標準字段包括:
- iss:JWT Token 的簽發者;
- sub:主題;
- exp:JWT Token 的過期時間;
- aud:接收 JWT Token 的一方;
- iat:JWT Token 的簽發時間;
- nbf:JWT Token 的生效時間;
- jti:JWT Token 的唯一標識(ID)。
Payload 示例如下所示:
{
"id": 2,
"userID": "user-p7q78j",
"nbf": 1527931805,
"iat": 1527931805
}
(3) Signature 簽名介紹
Signature 是 Token 的簽名部分,其生成方式如下:
- 使用 Base64 對 header.payload 進行編碼;
- 使用密鑰(Secret)對編碼后的內容進行加密,加密后的內容即為 Signature。
密鑰相當于一個密碼,存儲在服務端,通常通過配置文件設置密鑰的值。
最終生成的 Token 如下所示:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MzkwNzgwMDUsImlhdCI6MTczNTQ3ODAwNSwibmJmIjoxNzM1NDc4MDA1LCJ4LXVzZXItaWQiOiJ1c2VyLXc2aXJrZyJ9.GromRG7kK90UfU_Q5iOSHs_xE-zSk0e0HLHqJQUjYMU
簽名后,服務端會返回生成的 Token。客戶端在下次請求時會攜帶該 Token,服務端收到 Token 后會解析出 header.payload,然后使用相同的加密算法和密碼對 header.payload 再次加密,并將加密后的 Token 與收到的 Token 進行比對。如果二者相同,則驗證通過;如果不相同,則返回 HTTP 401 Unauthorized 錯誤。
3. JWT Token 生成示例
下述代碼展示了具體如何生成一個 JWT Token,通過代碼可以詳細的了解 Token 生成的方式:
#!/bin/bash
# 定義Header
HEADER='{"alg":"HS256","typ":"JWT"}'
# 定義Payload
PAYLOAD='{"exp":1739078005,"iat":1735478005,"nbf":1735478005,"x-user-id":"user-w6irkg"}'
# 定義Secret(用于簽名)
SECRET="Rtg8BPKNEf2mB4mgvKONGPZZQSaJWNLijxR42qRgq0iBb5"
# 1. Base64編碼Header
HEADER_BASE64=$(echo -n "${HEADER}" | openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n')
# 2. Base64編碼Payload
PAYLOAD_BASE64=$(echo -n "${PAYLOAD}" | openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n')
# 3. 拼接Header和Payload為簽名數據
SIGNING_INPUT="${HEADER_BASE64}.${PAYLOAD_BASE64}"
# 4. 使用HMAC SHA256算法生成簽名
SIGNATURE=$(echo -n "${SIGNING_INPUT}" | openssl dgst -sha256 -hmac "${SECRET}" -binary | openssl base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n')
# 5. 拼接最終的JWT Token
JWT="${SIGNING_INPUT}.${SIGNATURE}"
# 輸出JWT Token
echo "Generated JWT Token:"
echo "${JWT}"