JWT 是什麼
JWT 很常被拿來和 Session 比較,但它們其實不是同一層東西。Session 是一種登入狀態管理方式;JWT 是一種 token 格式,用來以 JSON claims 的形式在不同系統之間傳遞可驗證的聲明。
一句話回答:JWT(JSON Web Token)是 RFC 7519 定義的一種 compact、URL-safe claims 表示格式,常見形式是 JWS:由 Header、Payload、Signature 三段 Base64URL 組成;Signature 可以證明 token 沒被竄改,但 Payload 預設只是編碼不是加密,所以不能放敏感資料。使用 JWT 時必須驗證簽章、演算法、issuer、audience、expiration、not before、subject、jti 等 claims,並處理短效、撤銷、金鑰輪換與儲存位置問題。
一、 必考觀念
1. JWT 是 token 格式,不是登入架構本身
JWT 可以出現在很多地方:
- access token。
- id token。
- service-to-service token。
- magic link token。
- email verification token。
- temporary authorization token。
但不是所有 token 都是 JWT。
例如:
opaque session id:8cF2...random...
JWT:xxxxx.yyyyy.zzzzzOpaque token 沒有可讀 payload,server 要查資料庫或 introspection;JWT 則把 claims 放在 token 裡,接收方可以驗證簽章後讀取 claims。
2. JWT 常見格式是三段
header.payload.signature例如:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.
eyJzdWIiOiJ1c2VyXzEyMyIsImV4cCI6MTcxNjU0MDAwMH0
.
signature三段分別是:
| 區段 | 內容 |
|---|---|
| Header | token 類型、簽章演算法、key id |
| Payload | claims,例如 sub、exp、iss、aud |
| Signature | 對 header + payload 的簽章 |
3. JWT Payload 不是加密
JWT payload 只是 Base64URL encoded。
任何拿到 token 的人都可以 decode:
base64urlDecode(payload)所以不要放:
- 密碼。
- 身分證字號。
- 信用卡。
- refresh token。
- 私密個資。
- 內部安全註記。
Signature 只能證明資料沒有被改,不代表資料不能被看。
如果需要保密,要評估 JWE;但多數登入 access token 場景用的是 JWS,也就是簽章,不是加密。
二、 JWT 結構
1. Header
範例:
{
"alg": "RS256",
"typ": "JWT",
"kid": "key-2026-05"
}常見欄位:
| 欄位 | 說明 |
|---|---|
| alg | 簽章演算法,例如 HS256、RS256、ES256 |
| typ | token 類型,常見為 JWT |
| kid | key id,用於金鑰輪換 |
要注意:不能只相信 token header 裡的 alg。Server 必須有自己的允許演算法清單。
RFC 8725(JWT Best Current Practices)明確提醒實作時要避免弱簽章與演算法混淆,並且不要讓攻擊者控制驗證流程。
2. Payload
Payload 裡是 claims:
{
"sub": "user_123",
"iss": "https://auth.example.com",
"aud": "api.example.com",
"iat": 1716530000,
"nbf": 1716530000,
"exp": 1716533600,
"jti": "token_abc"
}常見 registered claims:
| Claim | 意義 |
|---|---|
| iss | issuer,誰簽發 |
| sub | subject,token 代表誰 |
| aud | audience,token 給誰用 |
| exp | expiration time,何時過期 |
| nbf | not before,何時之前不能用 |
| iat | issued at,何時簽發 |
| jti | JWT ID,token 唯一識別 |
RFC 7519 定義了這些 registered claims,但不是每個 claim 都強制存在。實務上應根據用途要求必要 claims。
3. Signature
Signature 用來防止 token 被竄改。
概念:
sign(
base64url(header) + "." + base64url(payload),
secretOrPrivateKey
)驗證時:
重新計算 signature
-> 比對 token signature
-> 驗證 claims重要的是:驗證簽章成功還不夠,claims 也要驗證。
三、 簽章演算法
1. HS256
HS256 是 HMAC + SHA-256,使用同一把 shared secret 簽發與驗證。
優點:
- 簡單。
- 效能好。
- 適合單一信任邊界。
缺點:
- 驗證方拿到 secret,就也能簽 token。
- 多服務共享 secret 風險大。
- secret 輪換要管理好。
適合小型系統或同一後端內部使用,但不適合把 secret 分發給很多服務。
2. RS256 / ES256
RS256 使用 private key 簽發、public key 驗證。
優點:
- 驗證方只需要 public key。
- auth server 保留 private key。
- 適合多服務驗證。
- 適合用 JWKS 做金鑰發布與輪換。
缺點:
- 實作與金鑰管理較複雜。
- 效能成本較高。
企業 SSO、OIDC、微服務常見 RS256 / ES256。
3. 演算法混淆風險
常見錯誤:
- 接受
alg: none。 - 沒有限制可用演算法。
- 把 RS256 public key 當 HS256 secret 使用。
- 根據 token header 動態決定驗證方式。
安全做法:
verifyJwt(token, {
algorithms: ['RS256'],
issuer: 'https://auth.example.com',
audience: 'api.example.com',
})不要讓 token 自己決定你要怎麼驗證它。
四、 Claims 驗證
1. 只驗簽章不夠
錯誤思路:
signature valid -> user logged in還要檢查:
| 檢查 | 為什麼 |
|---|---|
| exp | 避免過期 token 繼續使用 |
| nbf | 避免 token 提前使用 |
| iss | 確認由可信 issuer 簽發 |
| aud | 確認 token 是給這個服務用 |
| sub | 確認 subject 格式與狀態 |
| jti | 支援追蹤與撤銷 |
| scope / permissions | 授權檢查 |
2. exp 必須短
Access token 應該短效。
例如:
5 分鐘
15 分鐘
30 分鐘實際時間取決於產品風險與 refresh 機制。
不要簽一個有效一年的 access token,然後把它當 session 用。JWT 的撤銷成本高,生命週期應該更謹慎。
3. aud 和 iss 很重要
iss 防止接受其他系統簽發的 token。
aud 防止 token 被拿到不該使用的服務。
例如:
aud = api.example.com就不應該拿去呼叫:
billing.example.com除非 billing 也明確接受這個 audience。
4. Clock skew
分散式系統時,server 時鐘可能有少量誤差。
可以允許很小的 leeway:
30 秒
60 秒但不要給太大,否則 exp 和 nbf 會失去意義。
五、 JWT 和 Session 的差異
1. Session 是查狀態,JWT 是驗聲明
Session:
request 帶 session id
-> server 查 session store
-> 取得登入狀態JWT:
request 帶 JWT
-> server 驗簽章
-> 讀 claims
-> 根據 claims 判斷| 面向 | Session | JWT |
|---|---|---|
| 狀態 | server-side stateful | 常用於 stateless access token |
| 登出 | 刪 session 即可 | 需要短效、denylist 或 session check |
| 撤銷 | 容易 | 較難 |
| 多服務驗證 | 要查 session store | 驗 public key 即可 |
| Token 大小 | 通常很小 | claims 多會變大 |
| 權限更新 | 查 session / DB 即時 | token 內權限可能過期 |
2. JWT 不等於無狀態萬靈丹
如果你需要:
- 立即登出。
- 管理裝置。
- 撤銷單一 token。
- 權限即時變更。
- 偵測 session 風險。
那你往往又需要:
- token denylist。
- session id claim。
- refresh token store。
- introspection。
jtirevoke table。
這時系統就不再完全 stateless。
3. JWT 適合什麼?
JWT 適合:
- OAuth / OIDC access token 或 id token。
- 多服務之間傳遞可驗證 claims。
- 短效 access token。
- auth server 與 resource server 分離。
- 需要 public key 驗證的場景。
不一定適合:
- 普通單體 Web app 的長期登入 session。
- 需要立即撤銷的高風險後台。
- 把大量使用者資料塞進 token 的場景。
六、 JWT 儲存在哪裡?
1. Authorization Header
常見:
Authorization: Bearer eyJ...優點:
- 不會自動附加到所有請求。
- 比較不受 CSRF 影響。
- 適合 mobile app、API client。
缺點:
- Web 前端若存在 localStorage,容易被 XSS 讀走。
- 需要前端自己附上 header。
2. HttpOnly Cookie
也有人把 JWT 放 HttpOnly Cookie。
優點:
- JS 不能直接讀 token。
- 瀏覽器自動帶。
缺點:
- 要處理 CSRF。
- Cookie 大小有限制。
- JWT 仍然可能被當作 session cookie 使用。
如果 JWT 放 Cookie,就要像 session cookie 一樣設計:
- HttpOnly。
- Secure。
- SameSite。
- CSRF 防護。
- 合理 Path / Domain。
3. localStorage 的風險
localStorage 最大問題是 XSS。
如果攻擊者能執行 JS:
localStorage.getItem('accessToken')就可能直接偷 token。
所以 Web app 中長期保存 token 在 localStorage 要非常謹慎。若不得不用,access token 要短效,並且整個前端要非常重視 XSS 防護。
OWASP JWT Cheat Sheet 也提醒 client-side token storage 要小心 XSS 與 token sidejacking 風險。
七、 撤銷與登出
1. JWT 最常被問:如何登出?
如果 access token 是完全 stateless,而且還沒過期,server 沒有資料可查,就很難立即讓它失效。
常見策略:
| 策略 | 說明 |
|---|---|
| 短效 access token | 降低被盜後可用時間 |
| refresh token 可撤銷 | 登出時撤銷 refresh token |
| denylist | 把 jti 加入黑名單直到 exp |
| session id claim | token 內帶 sessionId,server 查 session |
| passwordChangedAt | 拒絕早於改密碼時間的 token |
| token introspection | resource server 查 auth server |
2. Access token 與 refresh token 分工
常見設計:
access token:短效 JWT,用來呼叫 API
refresh token:長效 opaque token,存在 server,可撤銷登出時:
撤銷 refresh token
可選:撤銷 session
可選:denylist 目前 access token 的 jti這樣即使 access token 還能用,也只能活很短時間。
下一篇會專門整理 Access Token 與 Refresh Token。
3. 權限變更問題
如果 JWT 裡放:
{
"permissions": ["user:delete"]
}使用者權限被移除後,舊 token 直到過期前仍可能帶著舊 permission。
解法:
- access token 短效。
- 高風險 API 即時查權限。
- token 帶 permission version。
- 權限變更時撤銷 session / refresh token。
- 使用 introspection 或 server-side authorization。
不要把 token claims 當永遠最新的權限真相。
八、 常見安全錯誤
1. 不驗簽章
只 decode JWT:
const payload = decode(token)這非常危險。Decode 只是解析,不代表 token 可信。
必須 verify:
const payload = verify(token, publicKey, {
algorithms: ['RS256'],
issuer: 'https://auth.example.com',
audience: 'api.example.com',
})2. 接受 alg: none
舊實作或錯誤設定可能接受未簽章 token。
安全做法:
- 明確指定允許演算法。
- 不接受 none。
- 使用成熟 JWT library。
- 測試惡意 token。
3. 把 JWT 當加密資料
不要在 payload 放:
{
"password": "...",
"creditCard": "...",
"nationalId": "..."
}JWT payload 可被讀取。
4. Secret 太弱
HS256 secret 如果太短或可猜,攻擊者可能暴力破解 secret 後自己簽 token。
使用:
- 足夠長的隨機 secret。
- Secret Manager / KMS。
- 定期輪換。
- 不提交到 Git。
5. 混用 token 類型
不要拿 id token 呼叫 API。
不要拿 email verification token 當 access token。
不要拿 refresh token 當 access token。
每種 token 都應該有:
- 用途。
- audience。
- lifetime。
- 驗證規則。
- 儲存位置。
九、 追問題庫
Q1:JWT 是什麼?
JWT 是 JSON Web Token,是 RFC 7519 定義的一種 compact、URL-safe claims 表示格式。常見形式是 header、payload、signature 三段組成,用簽章保證 claims 沒被竄改。
Q2:JWT Payload 安全嗎?
Payload 預設只是 Base64URL 編碼,不是加密。任何拿到 token 的人都能 decode,所以不能放敏感資料。
Q3:JWT 和 Session 差在哪?
Session 是 server-side state,request 帶 session id 後 server 查 session store。JWT 則是 token 裡帶 claims,server 驗簽章後讀 claims。Session 容易撤銷;JWT 適合短效、多服務驗證,但撤銷較麻煩。
Q4:JWT 可以登出嗎?
可以,但要設計。常見做法是 access token 短效、refresh token 可撤銷,必要時用 jti denylist 或 session id claim。完全 stateless 的長效 JWT 很難即時登出。
Q5:JWT 要放 localStorage 還是 Cookie?
沒有通用答案。Authorization header 避免 CSRF,但 Web 中若存在 localStorage 容易受 XSS 影響;HttpOnly Cookie 降低 JS 讀取風險,但要處理 CSRF。要看產品架構、XSS/CSRF 防護與 token 生命週期。
Q6:只驗 JWT signature 夠嗎?
不夠。還要驗 exp、nbf、iss、aud、sub、演算法、key、scope / permission 等 claims。
十、 實作題
題目:設計 JWT 驗證 middleware
需求:
- 接受
Authorization: Bearer。 - 只允許 RS256。
- 驗證 issuer、audience、expiration。
- 不信任 token header 決定演算法。
- 驗證 user 是否仍有效。
async function requireJwt(request: Request) {
const token = getBearerToken(request.headers.authorization)
if (!token) {
throw new UnauthorizedError()
}
const payload = await jwtVerifier.verify(token, {
algorithms: ['RS256'],
issuer: 'https://auth.example.com',
audience: 'api.example.com',
})
const user = await userRepository.findById(payload.sub)
if (!user || user.status !== 'active') {
throw new UnauthorizedError()
}
if (user.passwordChangedAt && payload.iat < toUnix(user.passwordChangedAt)) {
throw new UnauthorizedError()
}
return {
user,
tokenId: payload.jti,
scope: payload.scope,
}
}題目延伸:登出怎麼做?
async function logout(input: LogoutInput) {
await refreshTokenRepository.revoke(input.refreshTokenId)
if (input.accessTokenJti) {
await tokenDenylist.add({
jti: input.accessTokenJti,
expiresAt: input.accessTokenExp,
reason: 'logout',
})
}
}重點是:JWT 登出通常不是只刪前端 token。Server 端要能撤銷 refresh token,必要時追蹤 access token 的 jti。
十一、 資深視角
1. JWT 的價值在跨信任邊界傳遞 claims
JWT 很適合:
Auth Server 簽發
Resource Server 驗證
多服務不用每次查 Auth Server但如果是單體 Web app,只是想讓使用者登入,server-side session 可能更簡單、更可控。
2. Stateless 不等於不用設計狀態
很多系統一開始說 JWT stateless,後來又加:
- refresh token table。
- denylist。
- session id。
- key rotation。
- permission version。
- risk state。
這些都是狀態。
所以面試時要能說出:JWT 減少了某些查詢,但不會讓登入生命週期消失。
3. Token 要按用途分開
成熟系統會區分:
| Token | 用途 |
|---|---|
| access token | 呼叫 API |
| refresh token | 換新 access token |
| id token | 告訴 client 使用者身份資訊 |
| reset token | 重設密碼 |
| verification token | 驗證 email / phone |
不同 token 不應混用,也不應共用驗證規則。
總結
| 問題 | 回答重點 |
|---|---|
| JWT 是什麼 | compact、URL-safe claims token format |
| 結構 | header.payload.signature |
| Payload | 可讀,不是加密,不能放敏感資料 |
| Signature | 防竄改,不代表自動授權 |
| Claims | 要驗 exp、iss、aud、sub、jti、scope |
| 演算法 | 明確限制 HS256 / RS256 / ES256,不信任 header |
| 撤銷 | 短效 access token + refresh token revoke + denylist |
| Session 差異 | Session 查 server state,JWT 驗 claims |
| 儲存位置 | Header 避免 CSRF,Cookie 要防 CSRF,localStorage 怕 XSS |
一句完整的面試回答可以是:
JWT 是 JSON Web Token,是 RFC 7519 定義的 compact、URL-safe claims 表示格式,常見是 header、payload、signature 三段。Header 描述演算法與 key id,Payload 放 claims,例如 sub、iss、aud、iat、nbf、exp、jti,Signature 用 secret 或 private key 對 header + payload 簽章,防止被竄改。但 JWT payload 只是 Base64URL 編碼,不是加密,所以不能放敏感資料。使用 JWT 不能只 decode,也不能只驗 signature,還要限制演算法、驗 issuer、audience、expiration、not before、subject、token id 和 scope。JWT 適合短效 access token、多服務驗證和 OIDC/OAuth 場景;但撤銷比 server-side session 麻煩,所以通常搭配短效 access token、可撤銷 refresh token、jti denylist 或 session id claim。對普通 Web app,Session Cookie 可能更簡單可控。