iT邦幫忙

2026 iThome 鐵人賽

DAY 4
0
Software Development

從登入到授權-現代軟體的身分架構指南系列 第 4

Day 04|拆解 Token-Based Auth:Bearer、JWT、Access Token 與 Refresh Token

  • 分享至 

  • xImage
  •  

前言

上一篇我們把「傳統 HTTP 驗證方式」這個陣營講完了:Basic Auth、Digest Auth、API Key、Session-based 登入。它們的共同點是:你每次請求帶去的那個東西(帳密、API Key、Session ID),就是伺服器直接拿去比對或查表用的那件東西本尊,中間沒有再多一層選項。舉例來說:Basic Auth 就是帳密本人直接送過去比對;API Key 就是那把 Key 本身,伺服器一查就知道是哪個應用程式、專案或服務在呼叫 API;Session-based 就是那組 Session ID 本身,對應到伺服器上的一筆記錄。也因為這樣,伺服器不需要額外驗算什麼簽章,直接拿去比對或查表就能判斷要不要放行。

今天要進到另一個陣營:Token-Based Auth。這個陣營底下藏著好幾個常常被搞混的名詞:Bearer Token、JWT、Access Token、Refresh Token,不過在拆開個別名詞之前,先花一節篇幅認識這個陣營本身的共同特色,再一個一個往下看,最後跟 Session-based 登入做一次完整比較。

今天內容涵蓋:

  1. Token-Based Auth 陣營的共同特色
  2. Bearer Token:不問你是誰,只問你手上有沒有這張「通行證」
  3. JWT:一種自帶身份資訊的 Token
  4. Access Token 與 Refresh Token:短效與長效的分工
  5. Token-Based Auth vs Session-based 登入:兩種相反的思路,完整比較

一、Token-Based Auth 陣營的共同特色

上一節提到,傳統 HTTP 驗證方式的共同點是「你送出去的東西,本身就是伺服器要核對的憑證」——帳密、API Key、Session ID,伺服器直接比對或查表就好。

Token-Based Auth 走的是不一樣的路線:驗證用的憑證(Token)不是使用者原本就有的秘密,而是由驗證方(通常稱為授權伺服器)在驗證通過後,額外「簽發」出來的一張通行證。使用者不會每次都把帳密送出去,而是拿著這張後來拿到的 Token 去證明身份。

這樣的設計帶來幾個共同特色:

  • 原始機密只用一次:帳密只在登入那一刻用到,之後的請求都是靠 Token,就算 Token 外洩,攻擊者也拿不到原始密碼。
  • Token 可以設定有效期,過期就作廢,不像帳密本身沒有「有效期」的概念。
  • 驗證方式更有彈性:伺服器可以選擇查表比對,也可以像後面會介紹的 JWT 一樣直接驗證簽章,不用每次都查資料庫。

接下來就把這個陣營底下的幾個名詞——Bearer Token、JWT、Access Token、Refresh Token——一個一個拆開來看。


二、Bearer Token - 不問你是誰,只問你手上有沒有這張「通行證」

像是要幫產品加上「用 Google/Facebook 帳號登入」這種第三方登入,或是要設計一支同時給網頁、iOS、Android 等多個前端共用的 API,這些情境下最常用的就是 Token-Based Auth,而其中最基礎的一種規格就叫 Bearer Token

Bearer 是英文「持有人」的意思。Bearer Token 的邏輯簡單來說:系統不管你是誰,只看你手上有沒有這張 Token——只要拿得出來,系統就當你是這張 Token 原本要給的那個人,不會再進一步確認你的真實身份。這跟現金一樣不記名:一張千元鈔票,不管原本是發給誰的,只要現在在你手上,你就能拿去花,沒有人會去查這張鈔票的「原主人」是誰。

https://ithelp.ithome.com.tw/upload/images/20260909/20181928KVAKYG5GDn.png

流程如下:

  1. 使用者用帳密登入一次(或走其他驗證流程)。
  2. 伺服器驗證成功後,產生一個 Token,回傳給客戶端。
  3. 客戶端之後每次請求,都把這個 Token 放進 Authorization header,格式是 Authorization: Bearer <token>(是不是很眼熟?上一篇 API Key 那一節也出現過這個寫法)。
  4. 伺服器收到請求,看到 Bearer 開頭,取出後面的 Token 去驗證,通過才放行。

範例:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

這也是為什麼上一篇 API Key 那一節會出現 Authorization: Bearer <api_key> 這種寫法——Bearer 只是一個「驗證方案(auth scheme)」的名稱,宣告「我後面帶的是一個持有人式的 Token」,它沒有規定 Token 本身要長什麼樣子,可以是隨機字串,也可以是接下來要介紹的 JWT。這個用法正式定義在 RFC 6750(OAuth 2.0 Bearer Token Usage)

風險:Bearer Token 就跟現金一樣,誰拿到就是誰的,伺服器不會額外驗證「這個 Token 本來是不是發給你的」。一旦 Token 被攔截或洩漏,攻擊者不需要密碼,直接拿著 Token 就能冒充使用者,直到過期為止。所以傳輸 Bearer Token 一定要搭配 HTTPS,並讓有效期盡量短。


三、JWT - 一種自帶身份資訊的 Token

上面提到 Bearer Token 可以是任何格式的字串,而 JWT(JSON Web Token,定義在 RFC 7519) 是目前最常拿來當 Bearer Token 內容的一種格式。

跟前面提到的 API Key 最大的差異在於:API Key 只是一串隨機字串,本身不帶任何資訊,伺服器要知道這把 Key 屬於誰都得查資料庫;而 JWT 是把身份資訊直接編碼進 Token 本身,伺服器驗證簽名就能相信裡面的內容,不用每次都查資料庫。

JWT 由三段組成,用 . 分隔:

Header.Payload.Signature
  • Header(標頭):可以想成 JWT 的外包裝標籤,常見寫法是 typ: JWT,表示「這是一顆 JWT」;alg 則表示「它用哪一種演算法簽名」,例如 HS256RS256

  • Payload(內容):放實際要傳遞的資料,稱為 Claims(聲明)。常見的內建欄位有:

    • iss(Issuer):簽發者
    • sub(Subject):這個 Token 代表的對象,通常是使用者 ID
    • aud(Audience):這個 Token 是要給誰用的
    • exp(Expiration Time):過期時間
    • iat(Issued At):簽發時間
    • jti(JWT ID):這個 Token 的唯一編號,可以用來防止同一個 Token 被攔截後拿去重複使用(重放攻擊)

    除了這些內建欄位,也可以自訂欄位,例如使用者角色、權限等。

  • Signature(簽名):把 Header、Payload 加上一把只有伺服器知道的密鑰,一起算出來的簽名,用來確保內容沒有被竄改。

https://ithelp.ithome.com.tw/upload/images/20260909/20181928MAfbO5OP4R.png

流程如下:

  1. 使用者用帳密登入。
  2. 伺服器驗證成功,把使用者資訊塞進 Payload,加上密鑰算出簽名,組成完整的 JWT 回傳給客戶端。
  3. 客戶端把 JWT 存起來(Cookie 或 localStorage 都行),之後每次請求都帶著它,通常還是放在 Authorization: Bearer <JWT> 裡面。
  4. 伺服器收到請求,用同一把密鑰重新驗算簽名,比對是否一致。一致就代表內容沒被改過,直接從 Payload 讀出使用者資訊,不用再查資料庫。

實務上驗證 JWT,可以先拆成兩層來看。

第一層是驗證簽名。伺服器會用密鑰或公鑰重新驗算 JWT 的簽名,確認 Header 和 Payload 沒有被改過。這一步只能證明「這顆 Token 的內容沒有被竄改」。

第二層是檢查內容,也就是檢查 Payload 裡的 Claims 是否合理。例如:

  • exp:Token 是否已經過期。
  • iss:是不是由可信任的簽發者發出。
  • aud:這顆 Token 是不是發給目前這支 API 用的。
  • nbf:如果有設定,現在是不是已經可以使用。
  • alg:簽章演算法是不是伺服器允許的類型。

也就是說,API Server 會根據這些檢查結果,判斷這顆 Token 能不能被信任。簽名用來確認內容沒有被改過;Claims 則用來確認這顆 Token 是否仍在有效期限內、是否來自可信任的簽發者,並且是否適用於目前這支 API。

接著再看簽名本身。JWT 常見的簽章方式可以先分成兩種:

類型 常見演算法 簽署方式 適合情境
對稱式簽章 HS256 同一把 secret 負責簽署與驗證 同一個後端系統自己簽、自己驗
非對稱式簽章 RS256ES256 私鑰簽署,公鑰驗證 多個服務都需要驗證同一個 Token

對稱式簽章的重點是:誰拿到 secret,誰就能簽出新的 Token。因此它比較適合同一個後端系統內部使用,不適合把 secret 放在瀏覽器或手機 App 這種無法安全保管秘密的環境。

非對稱式簽章則是把「簽署」和「驗證」拆開。簽發者保管私鑰,用私鑰簽 Token;其他 API 服務只需要拿公鑰驗證簽名,不需要知道私鑰。這也是為什麼跨服務、多團隊的系統,通常會偏向使用 RS256ES256

💡 伺服器驗證 JWT 時,不應該完全相信 Token Header 裡的 alg 欄位。

比較安全的做法是:伺服器端先明確規定「允許哪些簽章演算法」,再依照這份允許清單驗證 Token。
否則攻擊者可能利用演算法混淆(Algorithm Confusion)等問題,誘使伺服器使用錯誤的驗證方式,進而接受偽造的 Token。

JWT 的常見風險:

  • 簽章不等於加密:Header 跟 Payload 只是用 Base64URL 編碼,不是加密。任何人拿到 JWT 都能解開看到裡面的內容,只是不能任意修改,因為改了內容簽名就驗不過。所以不要把密碼、身分證號這類機密資訊塞進 Payload。
  • 到期前不容易主動失效:JWT 一旦簽發,在到期(exp)之前,伺服器很難主動讓它失效。Session 可以直接在伺服器端刪掉一筆記錄,但 JWT 驗證時通常不查資料庫;除非額外實作黑名單或撤銷機制,否則「強制登出」會比較麻煩。
  • 演算法混淆:如果 API 沒有限制自己信任哪些簽章演算法,而是直接照 Token Header 裡的 alg 決定怎麼驗證,就可能被攻擊者利用。這也是前面提醒「不要完全相信 alg」的原因。

Token 存放在哪裡?

理解 JWT 的格式和驗證方式之後,下一個問題是:客戶端要把 Token 放在哪裡?

儲存位置 主要風險 說明
localStorage / sessionStorage XSS 竊取 頁面只要被注入惡意 JavaScript,就能直接讀走 Token,sessionStorage 差別只在分頁關閉會清掉
JavaScript 記憶體變數 重整頁面會消失 比存進 localStorage 安全一些,代價是重整頁面要重新登入
HttpOnly Cookie CSRF JavaScript 讀不到,但瀏覽器會自動帶上,要另外做 CSRF 防護
Backend-for-Frontend(後端代存) 需要額外的伺服器端狀態 瀏覽器只拿到一個 Session Cookie,真正的 Token 由後端保管,是目前公認對瀏覽器應用比較安全的做法

把 JWT 直接放進 localStorage 很方便,但網站只要有 XSS 漏洞,Token 就可能被整個偷走。所以不少團隊會改用 HttpOnly Cookie,或乾脆讓後端幫忙保管 Token,瀏覽器只留一個安全的 Session Cookie。


四、Access Token 與 Refresh Token - 短效與長效的分工

不管是單純的 Bearer Token 還是 JWT,都會遇到同一個兩難:Token 的有效期要設多長?

設太長,一旦洩漏攻擊者能用很久,風險高;設太短,使用者動不動就要重新登入輸入密碼,體驗很差。

解法是把 Token 拆成兩種,各司其職:

項目 Access Token Refresh Token
用途 真正拿去存取資源的憑證,放在每次 API 請求裡 只用來換一張新的 Access Token,本身不能直接存取資源
有效期 短,通常 15 分鐘〜1 小時 長,通常 7〜30 天
洩漏後的影響 影響有限,很快就過期 影響較大,攻擊者可以一直換到新的 Access Token

儲存建議(最佳實踐):Refresh Token 效期長、一旦洩漏影響範圍大,建議存放在 httpOnly Cookie,不建議像真正拿去存取資源的 Access Token 一樣直接放進 localStorage,降低它被 XSS 直接偷走的風險。

https://ithelp.ithome.com.tw/upload/images/20260909/20181928Q4XvvDeuch.png

流程如下:

  1. 使用者登入成功,伺服器一次發回一組 Access Token 跟 Refresh Token。
  2. 客戶端平常用 Access Token 存取資源。
  3. Access Token 過期後,客戶端拿 Refresh Token 去跟伺服器換一組新的 Access Token(通常也會換發一張新的 Refresh Token),不用讓使用者重新輸入密碼。
  4. 如果 Refresh Token 也過期或被伺服器主動撤銷,使用者才需要重新登入。

這套設計正式定義在 RFC 6749(OAuth 2.0 Authorization Framework) 裡,Refresh Token 被設計成只能拿去跟「授權伺服器」換新 Token,不會被送到真正提供資源的伺服器,降低洩漏時被直接拿去用的風險。也就是說,Access Token 是拿去敲資源伺服器的門,Refresh Token 則是拿回授權伺服器換新通行證的憑證,兩者不應該混用。

風險:Refresh Token 有效期長、權限等同於「重新登入一次」,一旦洩漏,攻擊者能持續換到新的 Access Token,而且這種濫用模式跟正常使用者行為很像,比 Access Token 洩漏更難被偵測到。目前業界的做法是Refresh Token Rotation:每次拿 Refresh Token 換新 Token 時,舊的 Refresh Token 就立刻失效、換發一張新的,一旦偵測到「已經失效的 Refresh Token」被拿來使用,就代表可能已經洩漏,能立刻撤銷整組 Token。除了 Refresh Token Rotation,常見的撤銷手段還有下面這兩種,簡单說就是「自己查一本名單」跟「直接去問發 Token 的人」:

  • 撤銷清单(revocation list):伺服器自己維護一張「已失效 Token 名單」,一旦有 Token 被發現有問題(例如使用者登入後發現帳戶被盜,需要提前宣告作廢),就把它加進這張名單。之後每次驗證前,伺服器先查一下這張名單,如果 Token 在名單上就直接拒絕,就算簽名驗證通過也一樣。
  • Token Introspection:不自己查名單,而是每次需要驗證時,直接發一次 API 請求去問發 Token 的那台授權伺服器:「這個 Token 現在還有效嗎?」,等它回覆有效或失效之後,才決定要不要放行。

這兩種做法的代價都是:每次驗證都得額外查一次(查名單或問伺服器),也就是把 JWT 本來「不用查資料庫、只驗簽名」的快,拿一部分回來換「能隨時強制失效」的能力,屬於效能與安全性之間的取捨。

💡 順帶一提,Access Token 不一定要長得像 JWT。

JWT 屬於 Self-contained Token:Token 本身就可以攜帶使用者、權限、過期時間等資訊,Resource Server 驗證簽章與相關 Claim 後,就能直接判斷 Token 的內容與有效性。

相對地,Opaque Token(不透明 Token,也常被稱為 Reference Token) 通常只是一串隨機、唯一,而且對客戶端本身沒有意義的字串。

它不會把授權資訊直接放在 Token 裡,而是把 Token 當成一把「查詢鑰匙」。Resource Server 必須查詢資料庫、快取,或透過 Token Introspection 向 Authorization Server 查詢,才能知道這顆 Token 是否有效、代表誰,以及擁有哪些權限。

所以簡單來說:JWT 是把資訊放在 Token 裡;Opaque Token 則是把資訊留在伺服器端,Token 本身主要負責當索引。

OAuth 2.0 本身並沒有規定 Access Token 一定要採用哪一種格式。實際要使用 JWT 還是 Opaque Token,取決於 Authorization Server 的設計。


五、Token-Based Auth vs Session-based 登入 - 兩種相反的思路

到這裡,Token-Based Auth 陣營底下的幾個名詞都拆開看過了。這裡特別挑 Session-based 登入出來比較,而不是整個「傳統 HTTP 驗證方式」陣營,是因為 Basic Auth、Digest Auth、API Key 本質上都是「每次請求重新驗證一次」,並沒有「在伺服器端維持登入狀態」這件事。只有 Session-based 登入才會面對這個問題,而且它跟 Token-Based Auth 剛好給出相反的答案——這就是為什麼下面挑 Session-based 登入來做完整比較:

比較項目 Session-based 登入 Token-Based Auth(以 JWT 為例)
狀態放在哪 伺服器端(Session) 客戶端自己帶著(Token 本身)
伺服器要不要查資料庫 要,靠 Session ID 查記錄 不用,驗證簽名就能相信內容
強制登出 容易,把伺服器上的 Session 刪掉就好,立即生效 比較困難,Token 到期前很難主動撤銷
擴展到多台伺服器 需要額外設計,讓每台伺服器都查得到同一份 Session 天生好擴展,任何一台伺服器都能獨立驗證
CSRF/XSS 風險 Cookie 搭配 HttpOnly、Secure、SameSite 可以有效防 XSS 直接讀取,但要另外處理 CSRF 存放方式沒選好就容易出事:放 localStorage 容易被 XSS 偷走,放 HttpOnly Cookie 又要處理 CSRF
適合場景 同一個網站、後端可控的系統 跨服務、跨網域、第三方串接

兩者的差別像是:Session-based 登入是「先辦一張會員卡,之後只要出示卡號,店員再自己回後台查這張卡對應的是誰」;Token-Based Auth 則是「每次都自己帶著一份寫好身份資訊、還蓋上店家簽名的證明文件,店員只要驗證那個簽名是不是真的,就能直接相信文件內容,不用再回頭查資料」。兩者沒有絕對的優劣,實務上很多系統甚至會混用——例如網頁前端用 Session,對外開放的 API 用 JWT。


小結

概念 說明
Bearer Token 一種驗證方案,規定怎麼帶著 Token 送出去;像持有人式的通行證,誰拿著就代表是誰,內容格式不限
JWT Token 的一種格式,把身份資訊編碼進 Token 本身,伺服器驗證簽名就能相信內容
Access Token 短效,真正拿去存取資源
Refresh Token 長效,用來換新的 Access Token

今天把 Token-Based Auth 底下的 Bearer、JWT、Access Token、Refresh Token 都拆開講完了,也跟 Session-based 登入做了完整比較。到目前為止,我們已經拼出了單一系統要怎麼驗證身份、怎麼維持登入狀態的完整拼圖。但如果使用者要用同一個帳號登入好幾個不同的系統呢?這就是 SSO(單一登入) 要解決的問題,內容也不少,留到下一篇專門來談。


參考資源


上一篇
Day 03|HTTP 驗證機制:Basic Auth、Digest Auth、API Key 與 Session
下一篇
Day 05|SSO : 一次登入,通行所有系統
系列文
從登入到授權-現代軟體的身分架構指南9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言