JWT 時間驗證:怎麼判斷 exp、iat 與 nbf?
DDEVELOPER
開發發布: 7 分鐘閱讀

JWT 時間驗證:怎麼判斷 exp、iat 與 nbf?

剛簽發的 JWT 就被 API 拒絕了,要把有效期限延長嗎?先看驗證端的時鐘。如果它慢了幾秒,權杖可能還沒生效,即使距離到期還有一個小時。以下用一組時間戳記和一個預約開放的邀請連結,說明簽發與驗證時容易遇到的五個問題。

日文原文發布: 2026-04-17

先讀懂權杖裡的時間

JWT(JSON Web Token)是傳遞宣告(claim)的格式。每項宣告都有名稱和值。例如,邀請權杖用 sub 指出受邀使用者,用 exp 記錄期限。讀出這些值只能得知簽發者聲稱的內容,接收端還要驗證。

本文採用 JWS 的緊湊格式,以句點分隔三段:標頭、包含宣告的 JSON 酬載,以及簽章值。三段都使用 Base64url 編碼,持有權杖的人不需要金鑰就能解碼前兩段、讀取內容。若要加密內容,JWT 也可採用 JWE 格式。

範例採用 HS256:簽發端以共用金鑰,對「已編碼的標頭、句點、已編碼的酬載」計算 HMAC-SHA-256。接收端的函式庫用同一把金鑰重新計算,再與第三段比對。竄改 exp 會使比對失敗;單純經過一段時間,簽章值卻不會改變。因此 API 還要檢查用途、接收對象與目前時間。持有共用金鑰就能簽發權杖,所以不能把金鑰交給受邀使用者的瀏覽器。

RFC 7519 的 NumericDate 從 UTC 1970 年 1 月 1 日零時起算,以秒為單位,不計閏秒,也可以有小數。JavaScript 的 Date.now() 回傳毫秒;若要用整數秒記錄簽發時間,可寫成 Math.floor(Date.now() / 1000)。RFC 7519 將 exp、iat、nbf 列為選用欄位,哪些必填由使用權杖的應用程式決定。

表格若超出畫面寬度,可左右捲動。

欄位意義解讀方式
exp到期時間容許誤差為零時,從這一刻起拒絕使用。
iat簽發時間簽發者建立這枚權杖的時間。
nbf生效時間容許誤差為零時,最早從這一刻開始接受。
auth_time身分驗證時間(OpenID Connect)使用者完成身分驗證的時間。

以下是用來比較時間的酬載(payload)。後面的邀請範例還會檢查簽章與接收對象。

{
  "sub": "user_123",
  "iat": 1713340800,
  "nbf": 1713340800,
  "exp": 1713344400
}

一、時鐘有落差:還沒生效,還是已經過期?

範例中的權杖一簽發就生效,一小時後到期。假設其餘條件都通過,容許誤差為零:剛簽發時,驗證端的時鐘快了 5 秒,權杖仍在有效期間內;如果慢了 5 秒,就會因為還沒到 nbf 而被拒絕。若錯誤訊息指出權杖已過期,請先把驗證端的目前時間和 exp 換成相同的秒數單位再比對。

RFC 7519 第 4.1.4、4.1.5 節允許實作為時鐘落差保留少量寬限。下表是用 jsonwebtoken 9.0.2 和 jose 6.1.0 驗證上述權杖的結果;30 秒是本例的設定值。

表格若超出畫面寬度,可左右捲動。

驗證端的時間容許誤差 0 秒容許誤差 30 秒
nbf 前 30 秒尚未生效接受
剛好等於 nbf接受接受
剛好等於 exp已過期接受
exp 後 30 秒已過期已過期

兩套函式庫分別在 verify 和 jwtVerify 接受 clockTolerance: 30。調大這個值,會讓權杖提早可用,也會延後拒絕過期權杖的時間。請監測時鐘同步狀態,再依服務可接受的範圍設定。

二、簽發時間與使用者登入時間是兩回事

今天剛換發的權杖,可能來自昨天登入的使用者。若要確認距離上次身分驗證過了多久,OpenID Connect 可在驗證要求中指定 max_age;此時回傳的 ID 權杖必須包含 auth_time,用戶端可據此檢查經過的時間。

若其餘條件都符合,前述兩個版本在未設定權杖最大年齡時,都會接受 iat 比驗證端時間晚 120 秒的權杖。即使把權杖的最大年齡設為 300 秒、容許誤差設為零,jsonwebtoken 的 maxAge 仍會放行,jose 的 maxTokenAge 則會拒絕。若服務要求簽發時間不得落在未來,請用實際採用的函式庫測試這個條件。單一權杖的期限與整段登入工作階段的期限也要分開管理。

三、預約開放的邀請,七天要從哪一天算?

假設現在簽發邀請,滿八天後才開放,開放後可用七天。jsonwebtoken 的 expiresIn: "7d" 通常從簽發時間起算;照這個設定,簽發滿七天就到期,比開放時間早了整整一天。若要讓收件人在開放後有完整七天,應先算出 nbf,再由 nbf 推算 exp。

// jwt 為 jsonwebtoken;releaseDate 為排定開放時間的 Date 物件。
// secret 是妥善保管的 HS256 共用金鑰,使用至少 256 位元的隨機值。
if (typeof userId !== "string" || !userId) throw new Error("請指定受邀使用者");
const nbf = Math.ceil(releaseDate.getTime() / 1000);
if (!Number.isFinite(nbf)) throw new Error("開放時間無效");
const exp = nbf + 7 * 24 * 60 * 60;
const token = jwt.sign({ sub: userId, nbf, exp }, secret, {
  algorithm: "HS256", issuer: "invite-service", audience: "invite-api"
});

若排定時間含毫秒,程式會無條件進位到下一個整秒,再起算七天。若改為捨去小數,可能提早最多 999 毫秒開放。容許誤差為零時,驗證端從 nbf 當下開始接受,到 exp 當下就拒絕。若開放時間必須精準,仍要讓時鐘保持同步;驗證端的時鐘太快,即使不設寬限也可能提早放行。

四、更新權杖輪替與登入期限分別處理

JWT 的 exp 是絕對時間。使用權杖或延長工作階段,都不會改變已簽發權杖內的 exp。OAuth 的更新權杖(refresh token)也可以是不含可解讀欄位的字串,由伺服器管理上次使用時間與撤銷狀態。「閒置多久後失效」和「從登入起最長可用多久」是兩項獨立條件。

輪替可用來偵測重複使用。每次更新時,授權伺服器簽發新的更新權杖,讓舊權杖失效,並保留兩者的關聯。如果舊權杖再次出現,就撤銷同一系列中仍有效的更新權杖。依 RFC 9700,對公開用戶端發出更新權杖時,授權伺服器必須採用傳送端繫結的更新權杖或權杖輪替,以偵測同一枚更新權杖遭到重複使用。

五、看得到到期時間,不代表邀請有效

解碼器可以顯示酬載,即使簽章無效也一樣。只改 exp,畫面會顯示新的到期時間,但原有簽章無法通過驗證。接收端應使用受信任簽發者的金鑰,並指定這個 API 接受的演算法、簽發者與接收對象。

// sessionUserId 取自已通過身分驗證的登入工作階段。
if (typeof sessionUserId !== "string" || !sessionUserId) {
  throw new Error("請先登入");
}
const claims = jwt.verify(token, secret, {
  algorithms: ["HS256"], issuer: "invite-service", audience: "invite-api",
  subject: sessionUserId, clockTolerance: 0
});
if (!Number.isFinite(claims.exp) || !Number.isFinite(claims.nbf)) {
  throw new Error("邀請權杖必須包含 exp 與 nbf");
}
if (claims.exp - claims.nbf > 7 * 24 * 60 * 60) {
  throw new Error("邀請的有效期間不得超過七天");
}

即使簽章正確,簽發端仍可能誤設過長的期限,因此 API 也要檢查邀請從生效到到期最多七天。本例的 aud 是專門處理邀請的服務 invite-api;sub 則是受邀使用者,必須與登入者相符。若同一簽發者和接收服務處理多種用途的權杖,還要分別訂出驗證條件。

函式庫能檢查 exp 是否已過期,並不表示它會拒絕沒有 exp 的權杖。jose 的 jwtVerify 可用 requiredClaims: ["exp", "nbf"] 要求兩個欄位都存在。允許的演算法與金鑰來源應由驗證端設定,不能交給尚未驗證的權杖標頭決定。

實作時如何測試

  • 先約定哪些欄位必填,以及是否限制權杖從簽發到使用的最長時間。
  • 在測試中固定驗證端時間,分別測試生效前一刻、生效當下、到期前一刻和到期當下。
  • 先竄改已簽署的酬載,再另外用受信任的測試金鑰簽發接收對象錯誤或缺少必填欄位的權杖。確認每個案例都因預期的原因遭拒。
  • 輪替公開金鑰時,可依 kid 從已信任的簽發者金鑰集合中選取金鑰。不同用途的權杖應有各自的驗證規則。

驗證通過後,服務仍要檢查權限

服務確認了權杖與使用者身分,還要判斷是否允許這次操作。伺服器上的檔案則由 Unix 權限保護。例如,其他使用者能讀取私鑰時,OpenSSH 可能拒絕使用它,常見處理方式是將權限設為 600。可參閱 chmod 權限指南,比較 600、644 與 755 各自開放哪些操作。

參考資料與來源

編輯說明

本文使用 AI 協助編輯,並於發布前由編輯確認。內容仍可能包含事實、解讀或時效上的錯誤;進行重要判斷前,請查閱所列的一手資料或官方文件。

相關工具

相關文章