# 第 14 篇:JWT 與 JWS 格式解析 - 呼叫 API 的通行證
## 前言:為什麼需要學 JWT?
在實作 App Attest 或 Play Integrity 驗證時,你會遇到一個共同的問題:**如何呼叫 Apple 或 Google 的 API?**
### 🔑 實際場景
**驗證 App Store Receipt:**
```
你的伺服器:「Apple,請幫我驗證這個 receipt」
Apple API:「你是誰?證明你有權限呼叫這個 API」
你的伺服器:「這是我的 JWT token(auth token)」
Apple API:「驗證通過,這是 receipt 的內容」
```
**呼叫 Google API:**
```
類似流程,也需要 JWT 當作 auth token
```
### ❓ 問題來了
**你需要產生 JWT token,但:**
- JWT 是什麼格式?
- 為什麼是這種格式?
- 怎麼產生 JWT?
**這就是本文要解答的問題。**
目的很單純:讓你理解 JWT 的格式,知道如何產生一個合法的 JWT token,才能順利呼叫 Apple/Google 的 API。
## JWT 是什麼?
### 📜 正式定義
**JWT** = **J**SON **W**eb **T**oken
- 一種開放標準(RFC 7519)
- 用 JSON 格式來表示「聲明」(claims)
- 可以被數位簽章或加密
- 主要用於身份驗證和資訊交換
### 🎫 生活類比:電影票
想像 JWT 就像一張電影票:
```
電影票
──────────────────
電影:復仇者聯盟
場次:2024/10/02 19:30
座位:A15
票價:$350
──────────────────
[防偽浮水印] [QR Code]
```
**電影票的特點:**
- ✅ 自己帶著所有資訊(電影名稱、時間、座位)
- ✅ 有防偽機制(浮水印、QR Code)
- ✅ 一看就懂(不需要查詢系統)
**JWT 的特點:**
- ✅ 自己帶著所有聲明(user ID、權限、過期時間)
- ✅ 有數位簽章(防止竄改)
- ✅ 可以解碼查看(Base64 編碼,不是加密)
## JWT 的結構
### 🧩 三個部分
JWT 由三個部分組成,用點號 `.` 連接:
```
Header.Payload.Signature
```
**實際的 JWT:**
```
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.
eyJpc3MiOiJjb20uZXhhbXBsZS5hcHAiLCJleHAiOjE3MDk4NzY1NDN9.
MEUCIQDxF8H3fK9b_L2vX1pN8wQ7R5tY6zM3nJ4kP9sD2fG1wIgE7vH9Kq
```
看起來像亂碼?別擔心,這只是 **Base64URL 編碼**,不是加密!
### 📦 Part 1: Header(標頭)
**用途:** 說明這個 token 的類型和簽章演算法
**編碼前(JSON):**
```json
{
"typ": "JWT",
"alg": "ES256"
}
```
**編碼後(Base64URL):**
```
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9
```
**欄位說明:**
**typ (Type)**:
- 固定值:`"JWT"`
- 表示這是一個 JWT token
**alg (Algorithm)**:
- 說明用什麼演算法簽章
- 常見值:
- `ES256`:ECDSA + SHA-256(Apple/Google 常用)
- `RS256`:RSA + SHA-256(傳統方式)
- `HS256`:HMAC + SHA-256(需妥善金鑰管理,不適合跨實體驗證)
### 📝 Part 2: Payload(載荷)
**用途:** 實際要傳遞的資料(聲明)
**編碼前(JSON):**
```json
{
"iss": "com.example.myapp",
"iat": 1709876543,
"exp": 1709880143,
"aud": "appstoreconnect-v1"
}
```
**編碼後(Base64URL):**
```
eyJpc3MiOiJjb20uZXhhbXBsZS5hcHAiLCJleHAiOjE3MDk4NzY1NDN9
```
**標準欄位(Registered Claims):**
| 欄位 | 全名 | 說明 | 範例 |
|------|------|------|------|
| `iss` | Issuer | 發行者(誰發的) | `"com.example.app"` |
| `sub` | Subject | 主題(關於誰) | `"user123"` |
| `aud` | Audience | 接收者(給誰用) | `"appstoreconnect-v1"` |
| `exp` | Expiration | 過期時間(Unix timestamp) | `1709880143` |
| `iat` | Issued At | 發行時間(Unix timestamp) | `1709876543` |
| `nbf` | Not Before | 生效時間(Unix timestamp) | `1709876543` |
**為什麼需要這些欄位?**
**iss (Issuer)**:
```
用途:標明是誰發的這個 token
例如:你的 App Bundle ID
Apple 會檢查:這個 token 是你發的嗎?
```
**exp (Expiration)**:
```
用途:限制 token 的有效時間
例如:10 分鐘後過期
Apple 會檢查:這個 token 過期了嗎?
```
**aud (Audience)**:
```
用途:說明這個 token 是給誰用的
例如:給 "appstoreconnect-v1" 用
Apple 會檢查:這個 token 是給我的嗎?
```
### ✍️ Part 3: Signature(簽章)
**用途:** 證明 token 沒有被竄改
**如何產生簽章:**
```
1. 把 Header 和 Payload 用點號連接
data = base64url(header) + "." + base64url(payload)
2. 用私鑰簽章這段資料
signature = sign(data, private_key, algorithm)
3. 把簽章也做 Base64URL 編碼
encoded_signature = base64url(signature)
```
**完整的 JWT:**
```
base64url(header) + "." + base64url(payload) + "." + base64url(signature)
```
**為什麼這樣設計?**
```
駭客想竄改 Payload:
1. 解碼 Payload(很簡單,Base64 而已)
2. 修改內容(例如:把過期時間延長)
3. 重新編碼 Payload
但是:
4. 沒有私鑰,無法產生正確的簽章
5. Apple 驗證簽章時會失敗 ❌
```
## Base64URL 編碼:為什麼不是 Base64?
### 🔤 Base64 vs Base64URL
**問題:** 標準 Base64 會產生 `+`、`/`、`=` 這些字元
```
標準 Base64:
SGVsbG8gV29ybGQh+/==
問題:
├── + 在 URL 中有特殊意義(空格)
├── / 在 URL 中是路徑分隔符
└── = 在 URL 中是參數分隔符
```
**解決方案:Base64URL**
```
替換規則:
+ → -(減號)
/ → _(底線)
= → 移除(不要 padding)
結果:
SGVsbG8gV29ybGQh-_
```
**為什麼重要?**
JWT 常常放在 URL 或 HTTP Header 中:
```
Authorization: Bearer eyJhbGc...(JWT token)
```
使用 Base64URL 確保:
- ✅ 可以安全地放在 URL 中
- ✅ 可以安全地放在 HTTP Header 中
- ✅ 不會被誤解為特殊字元
## JWS:有簽章的 JWT
### 📋 什麼是 JWS?
**JWS** = **J**SON **W**eb **S**ignature
- JWT 只是定義格式
- JWS 定義如何簽章
**關係:**
```
JWT:定義「長什麼樣子」
JWS:定義「如何簽章」
實務上:
大部分人說的 JWT 其實是 JWS(有簽章的 JWT)
```
### 🔐 簽章演算法
#### ES256(ECDSA + SHA-256)⭐ 推薦
**特性:**
- 使用橢圓曲線密碼學(第3篇學過的 ECDSA)
- 公鑰小、簽章小、速度快
- Apple 和 Google 都使用
**金鑰:**
```
私鑰:用來簽章(只有你有)
公鑰:用來驗證(可以公開)
```
**產生流程:**
```
1. 準備資料
data = base64url(header) + "." + base64url(payload)
2. 計算 hash
hash = SHA256(data)
3. 用私鑰簽章
signature = ECDSA_sign(hash, private_key)
4. Base64URL 編碼
encoded = base64url(signature)
```
#### RS256(RSA + SHA-256)
**特性:**
- 使用 RSA 演算法
- 相容性好(老系統都支援)
- 金鑰較大、簽章較大
#### HS256(HMAC + SHA-256)⚠️ 不適合跨實體驗證場景
**特性:**
- 使用共享密鑰(symmetric key)
- 發送方和接收方都有相同的密鑰
- 無法提供「不可否認性」
**為什麼不適合 API 認證?**
```
問題:
├── 伺服器需要知道你的密鑰
├── 如果密鑰洩漏,伺服器也能偽造你的 token
└── 無法證明「確實是你發的」
適用場景:
└── 自己的系統內部使用(前後端共享密鑰)
```
## 產生 JWT 的基本流程
### 📝 步驟總覽
```
步驟 1:準備私鑰
└── 從 Apple Developer 下載 .p8 私鑰檔案
步驟 2:建立 Header
└── { "alg": "ES256", "typ": "JWT" }
步驟 3:建立 Payload
└── { "iss": "你的 issuer ID", "exp": 過期時間, ... }
步驟 4:簽章
└── 用私鑰簽章 Header 和 Payload
步驟 5:組合
└── header.payload.signature
```
### 🔑 關鍵:私鑰格式
Apple 給你的私鑰是 **PKCS#8 PEM 格式**:
```
-----BEGIN PRIVATE KEY-----
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg...
-----END PRIVATE KEY-----
```
**這是什麼?**
- PKCS#8:私鑰的標準格式
- PEM:Base64 編碼的文字格式(第8篇講過)
- 內容:EC 私鑰(P-256 曲線)
### 🛠️ 使用程式庫
**Python 範例(概念):**
```python
import jwt
import time
# 讀取私鑰
with open('AuthKey_XXXXXXXXXX.p8', 'r') as f:
private_key = f.read()
# 建立 payload
payload = {
'iss': 'your-issuer-id',
'iat': int(time.time()),
'exp': int(time.time()) + 600, # 10 分鐘後過期
'aud': 'appstoreconnect-v1'
}
# 產生 JWT
token = jwt.encode(
payload,
private_key,
algorithm='ES256',
headers={'kid': 'your-key-id'} # Key ID
)
```
**重點概念:**
- 使用現成函式庫(`PyJWT`、`jose`等)
- 不需要手動處理 Base64URL、簽章等細節
- 理解格式比實作細節重要
## 驗證 JWT 的流程
### ✅ 接收方如何驗證?
```
收到 JWT:header.payload.signature
步驟 1:分離三個部分
├── header_b64 = "eyJhbGc..."
├── payload_b64 = "eyJpc3M..."
└── signature_b64 = "MEUCIQDx..."
步驟 2:解碼 Header
├── 得知使用的演算法(例如:ES256)
└── 得知 Key ID(如果有)
步驟 3:解碼 Payload
├── 檢查 exp(過期時間)
├── 檢查 iss(發行者)
└── 檢查 aud(接收者)
步驟 4:驗證簽章
├── 重新計算:data = header_b64 + "." + payload_b64
├── 用公鑰驗證簽章
└── 驗證通過 ✅ 或失敗 ❌
```
### 🔍 常見的驗證失敗原因
**1. 簽章驗證失敗**
```
原因:
├── Payload 被竄改
├── 使用錯誤的私鑰簽章
└── 使用錯誤的公鑰驗證
```
**2. Token 過期**
```
原因:
├── exp 時間已過
└── 需要重新產生 token
```
**3. Issuer 不符**
```
原因:
├── iss 欄位不是預期的值
└── 可能是錯誤的 token
```
## JWT 與其他格式的對比
### 📊 為什麼 Apple/Google API 選擇 JWT?
| 特性 | JWT | X.509 憑證 | CBOR |
|------|-----|-----------|------|
| **可讀性** | ✅ 高(Base64 解碼即可) | ❌ 低(需要工具) | ❌ 低(二進位) |
| **自包含** | ✅ 帶著所有資訊 | ✅ 帶著公鑰和身份 | ✅ 帶著所有資訊 |
| **體積** | 中等 | 大 | 小 |
| **標準化** | ✅ RFC 7519 | ✅ X.509 | ✅ RFC 8949 |
| **Web 友善** | ✅ 很友善 | ⚠️ 需要轉換 | ❌ 需要轉換 |
| **適用場景** | API 認證、會話管理 | 身份憑證、信任鏈 | 資料傳輸 |
**為什麼 API 認證用 JWT?**
1. **簡單易用**:
- JSON 格式,開發者熟悉
- 不需要複雜的解析工具
2. **自包含**:
- Token 本身帶著所有資訊
- 不需要查詢資料庫
3. **跨平台**:
- 所有程式語言都有 JWT 函式庫
- 不需要處理 ASN.1 編碼
4. **HTTP 友善**:
- 可以放在 Header、URL、Cookie
- Base64URL 確保相容性
### 🔄 實際使用場景對比
**App Attest(裝置認證):**
```
格式:CBOR + X.509
原因:
├── 體積小(行動網路友善)
├── 安全性高(標準密碼學格式)
└── 包含完整憑證鏈
```
**API 認證(Apple/Google):**
```
格式:JWT
原因:
├── Web 標準
├── 容易實作
└── 自包含(不需要額外查詢)
```
## 本文小結
✅ **JWT 是什麼**:JSON Web Token,用 JSON + Base64URL + 簽章的格式
✅ **三個部分**:Header(類型和演算法)+ Payload(資料)+ Signature(簽章)
✅ **為什麼用 Base64URL**:確保可以安全放在 URL 和 HTTP Header
✅ **JWS**:有簽章的 JWT,實務上最常用
✅ **用途**:呼叫 Apple/Google API 時的 auth token
### 🎓 與前面文章的連結
**系列二第6篇(ECDSA)→ JWT 的簽章演算法**
- JWT 的 ES256 就是使用 ECDSA
- 還記得橢圓曲線和數位簽章嗎?JWT 在用同樣的技術
**第10篇(X.509)→ 不同的應用場景**
- X.509:用於身份憑證和信任鏈
- JWT:用於 API 認證和會話管理
- 兩者解決不同的問題
**第11篇(ASN.1)→ 不同的編碼方式**
- X.509 用 ASN.1 DER(二進位)
- JWT 用 JSON + Base64URL(文字)
- JWT 更簡單、更 Web 友善
### 📊 格式選擇的考量
```
選擇 X.509 憑證(ASN.1 DER):
├── 需要建立信任鏈
├── 需要長期有效的身份證明
└── 密碼學標準要求
選擇 JWT:
├── API 認證
├── 短期會話管理
└── Web/HTTP 友善
```
## 下一篇預告
第15篇《WebAuthn與裝置信任模型》會探討現代身份驗證的基礎協定,而第16篇《Apple App Attest 資料格式實例解析》會整合所有學到的格式知識:
- 如何解析完整的 attestation object?
- CBOR、X.509、ASN.1、PKCS#7 如何協同運作?
- 實際開發時如何處理這些格式?
你會發現,Apple 在不同場景使用不同的格式:
- App Attest:CBOR + X.509
- API 認證:JWT
- Receipt:PKCS#7
每種格式都有它的設計考量!
---
## 💡 補充資料
### 常見問題
**Q: JWT 是加密的嗎?**
A: 不是!JWT 只是 Base64URL 編碼,任何人都能解碼看到內容。簽章只是確保沒有被竄改,不是加密。
**Q: JWT 可以存敏感資訊嗎?**
A: 不建議!因為內容可以被解碼。只放不敏感但需要驗證的資訊(例如 user ID、權限)。
**Q: 為什麼不用對稱金鑰(HS256)?**
A: 對稱金鑰雙方都有相同密鑰,伺服器也能偽造你的 token。非對稱金鑰(ES256/RS256)只有你能簽章,更安全。
**Q: Token 過期了怎麼辦?**
A: 重新產生一個新的 token。這是設計上的安全機制,限制 token 的有效時間。
### 實用資源
**線上工具:**
- [jwt.io](https://jwt.io) - JWT 解碼和驗證工具
**程式庫:**
- **Python**: `PyJWT`, `python-jose`
- **Node.js**: `jsonwebtoken`, `jose`
- **PHP**: `firebase/php-jwt`
- **Java**: `jjwt`
**官方規格:**
- [RFC 7519 - JWT](https://tools.ietf.org/html/rfc7519)
- [RFC 7515 - JWS](https://tools.ietf.org/html/rfc7515)
### Apple API 相關文件
- [App Store Connect API - Authentication](https://developer.apple.com/documentation/appstoreconnectapi/generating_tokens_for_api_requests)
- [App Store Server API](https://developer.apple.com/documentation/appstoreserverapi)