程 JWKS 密鑰解析:RemoteJWKSet 接口全解析)
網(wǎng)絡(luò)安全認(rèn)證鑒權(quán)后端【免費(fèi)下載鏈接】joseJWA, JWS, JWE, JWT, JWK, JWKS for Node.js, Browser, Cloudflare Workers, Deno, Bun, and other Web-interoperable runtimes項(xiàng)目地址https://gitcode.com/gh_mirrors/jo/jose點(diǎn)擊查看免費(fèi)下載本文圍繞 jose 庫中createRemoteJWKSet返回的密鑰解析函數(shù)RemoteJWKSet系統(tǒng)講解其函數(shù)簽名、實(shí)例屬性、內(nèi)置的緩存與冷卻cooldown機(jī)制以及jwksCache、customFetch、RemoteJWKSetOptions等配套選項(xiàng)的完整用法。讀者讀完將掌握如何在 Node.js、瀏覽器、Cloudflare Workers、Deno、Bun 等 Web-interoperable 運(yùn)行時(shí)中基于 OAuth 2.0 / OIDC 的jwks_uri端點(diǎn)實(shí)現(xiàn) JWT 驗(yàn)簽的自動(dòng)密鑰發(fā)現(xiàn)、緩存刷新與錯(cuò)誤處理并能結(jié)合源碼理解其底層實(shí)現(xiàn)。一、RemoteJWKSet 是什么RemoteJWKSet是調(diào)用createRemoteJWKSet后返回的一個(gè)可調(diào)用對(duì)象callable它本身是一個(gè)異步函數(shù)用于把 JWS JOSE Header 解析resolve為用于驗(yàn)簽的公鑰對(duì)象同時(shí)對(duì)象身上還掛載了若干描述其內(nèi)部緩存狀態(tài)的只讀屬性與方法。完整定義見 src/jwks/remote.ts 與 接口文檔。它可以直接傳給jwtVerify以及所有接受密鑰解析函數(shù)的消費(fèi)方如compactVerify、flattenedVerify、generalVerify等相關(guān)說明見 jwtVerify 文檔。也就是說你幾乎不需要直接調(diào)用RemoteJWKSet本身——把它作為getKey參數(shù)傳給驗(yàn)簽 API 即可。函數(shù)簽名? RemoteJWKSet( protectedHeader?: JWSHeaderParameters, token?: FlattenedJWSInput, ): PromiseCryptoKey參數(shù)類型說明protectedHeader?JWSHeaderParametersJWS 受保護(hù)頭包含alg、kid等用于選鍵的參數(shù)token?FlattenedJWSInput扁平化 JWS 輸入含protected、payload、signature等字段返回值PromiseCryptoKey即解析得到的 Web Crypto API 公鑰對(duì)象。[!NOTE] 該函數(shù)的用途是解析驗(yàn)簽用公鑰不會(huì)用于公鑰加密場(chǎng)景。二、實(shí)例屬性與方法詳解RemoteJWKSet除了可調(diào)用之外還通過Object.defineProperties安裝了五個(gè)實(shí)例成員實(shí)現(xiàn)見 src/jwks/remote.ts用于觀測(cè)和控制內(nèi)部的 JWKS 緩存狀態(tài)。成員類型語義coolingDownreadonly boolean自上次成功 fetch 之后冷卻窗口是否仍在生效即是否處于冷卻期內(nèi)freshreadonly boolean當(dāng)前緩存的 JWKS 是否仍在其 cacheMaxAge 有效期之內(nèi)reloadingreadonly boolean是否正有一個(gè) JWKS fetch 請(qǐng)求在途in flightreload()() Promisevoid主動(dòng)觸發(fā)一次 JWKS fetch繞過冷卻期jwks()() JSONWebKeySet \| undefined返回當(dāng)前緩存的JSONWebKeySet尚未 fetch 或未通過jwksCache播種時(shí)返回undefined三個(gè)布爾狀態(tài)的生命周期從 test/jwks/remote.test.ts 的createRemoteJWKSet manual reload測(cè)試可以清楚看到各狀態(tài)的流轉(zhuǎn)創(chuàng)建之初尚未發(fā)生任何 fetchcoolingDown false、fresh false、reloading false且jwks()返回undefined首次 fetch 成功后coolingDown與fresh立即變?yōu)閠ruefresh取決于cacheMaxAgecoolingDown取決于cooldownDuration調(diào)用reload()期間reloading truefetch 完成后回到false手動(dòng)修改jwks()返回值無效測(cè)試中JWKS.jwks()!.keys []之后再驗(yàn)簽依然成功說明該函數(shù)返回的是內(nèi)部不可變的快照/代理視圖直接改返回對(duì)象不會(huì)污染內(nèi)部緩存。reload() 的語義reload()是唯一主動(dòng)刷新手段它繞過cooldownDuration的冷卻限制強(qiáng)制拉取一次 JWKS。結(jié)合jwksCache使用時(shí)成功 fetch 的結(jié)果也會(huì)同步寫回外部緩存對(duì)象見下文。三、緩存與節(jié)流RemoteJWKSet 的底層行為要正確使用RemoteJWKSet必須理解其背后的兩級(jí)緩存策略實(shí)現(xiàn)見 src/jwks/remote.ts首次解析或緩存過期時(shí)若本地沒有緩存或緩存已超過cacheMaxAge先觸發(fā)一次 fetch本地解析失敗時(shí)若JWKSNoMatchingKey無匹配密鑰拋出且距離上次成功 fetch 已超過cooldownDuration則再次 fetch 并重試一次解析——這是為了讓遠(yuǎn)端輪換密鑰后本地緩存能在冷卻結(jié)束后盡快自動(dòng)更新冷卻期內(nèi)不會(huì)因?yàn)椤盁o匹配密鑰”而反復(fù)打爆遠(yuǎn)端端點(diǎn)這是防止濫用abuse的核心設(shè)計(jì)。整個(gè) fetch 過程由內(nèi)部fetchJwks完成src/jwks/remote.ts它要求 HTTP 響應(yīng)必須是200并將響應(yīng)體解析為 JSON超時(shí)或解析失敗分別拋出JWKSTimeout與JOSEError。并發(fā)與序列保護(hù)源碼通過reloadSequence/appliedSequence兩個(gè)遞增計(jì)數(shù)器確保較舊的 fetch 結(jié)果不會(huì)覆蓋較新的結(jié)果見 src/jwks/remote.ts 與 test/jwks/remote.test.ts 的 workerd 并發(fā)測(cè)試同時(shí)在 Cloudflare Workers 等隔離型運(yùn)行時(shí)中若存在上一個(gè)請(qǐng)求遺留的 in-flight fetch會(huì)被主動(dòng)作廢pendingFetch undefined見 src/jwks/remote.ts避免舊請(qǐng)求的 Promise 干擾新請(qǐng)求。選鍵規(guī)則選鍵嚴(yán)格遵循 RFC 語義先用 Header 的alg決定 JWK 的kty再用kid匹配 JWK 的kid若 Header 中存在同時(shí)尊重 JWK 上的use如sig與key_ops。必須恰好匹配到一個(gè)公鑰匹配不到拋JWKSNoMatchingKey匹配到多個(gè)拋JWKSMultipleMatchingKeys該錯(cuò)誤可迭代見 src/util/errors.ts。四、快速上手創(chuàng)建并使用 RemoteJWKSetconst JWKS jose.createRemoteJWKSet(new URL(https://www.googleapis.com/oauth2/v3/certs)) const { payload, protectedHeader } await jose.jwtVerify(jwt, JWKS, { issuer: urn:example:issuer, audience: urn:example:audience, }) console.log(protectedHeader) console.log(payload)這是 官方示例 中的標(biāo)準(zhǔn)用法createRemoteJWKSet只接收URL與可選的RemoteJWKSetOptions返回的JWKS函數(shù)直接充當(dāng)jwtVerify的密鑰解析器。jwtVerify內(nèi)部會(huì)依次調(diào)用RemoteJWKSet(protectedHeader, token)來解析公鑰見 src/jwt/verify.ts 的消費(fèi)方式。導(dǎo)入方式該能力從主入口jose以及子路徑j(luò)ose/jwks/remote均有命名導(dǎo)出相關(guān)導(dǎo)出見 src/index.tsimport { createRemoteJWKSet, jwksCache, customFetch } from jose // 或 import { createRemoteJWKSet, jwksCache, customFetch } from jose/jwks/remote五、RemoteJWKSetOptions全部可配置項(xiàng)創(chuàng)建RemoteJWKSet時(shí)可傳入以下選項(xiàng)完整說明見 RemoteJWKSetOptions 文檔默認(rèn)值與校驗(yàn)邏輯見 src/jwks/remote.ts 與validateDurationsrc/jwks/remote.ts選項(xiàng)類型默認(rèn)值說明timeoutDurationnumber50005 秒HTTP 請(qǐng)求超時(shí)時(shí)間毫秒。超時(shí)后請(qǐng)求被中止、驗(yàn)簽失敗。必須是非負(fù)整數(shù)cooldownDurationnumber3000030 秒上次成功 fetch 后的一段時(shí)間毫秒內(nèi)不再觸發(fā)新的 HTTP 請(qǐng)求防止濫用。不能為NaNcacheMaxAgenumber60000010 分鐘兩次成功 HTTP 請(qǐng)求之間的最大間隔毫秒即緩存有效期。不能為NaN源碼類型上還允許InfinityheadersRecordstring, string—隨 HTTP 請(qǐng)求發(fā)送的額外請(qǐng)求頭[jwksCache]JWKSCacheInput—外部可寫緩存對(duì)象見下文[customFetch]FetchImplementation全局fetch自定義 fetch 實(shí)現(xiàn)關(guān)于默認(rèn)請(qǐng)求頭創(chuàng)建解析器時(shí)源碼會(huì)為請(qǐng)求設(shè)置默認(rèn)請(qǐng)求頭src/jwks/remote.tsUser-Agentjose/v6.2.10瀏覽器環(huán)境為避免觸發(fā)不必要的 CORS preflight 而省略acceptapplication/json, application/jwk-setjson若你未自定義。test/jwks/remote.test.ts中對(duì)user-agent頭做了斷言test/jwks/remote.test.ts說明該默認(rèn)值是可觀測(cè)的。六、jwksCache面向無狀態(tài)云運(yùn)行時(shí)的持久緩存jwksCache是一個(gè)unique symbolsrc/jwks/remote.ts專為無法在兩次調(diào)用之間保留內(nèi)存緩存的云函數(shù)/邊緣運(yùn)行時(shí)設(shè)計(jì)如某些 Serverless 與 Workers 環(huán)境。[!WARNING] 該選項(xiàng)存在安全影響必須保證 JWKS 緩存對(duì)象只能被你自己的代碼寫入否則可能被注入惡意公鑰。傳入jwksCache后你提供的可寫對(duì)象承擔(dān)兩個(gè)職責(zé)見 jwksCache 文檔作為初始緩存如果對(duì)象攜帶合法的jwks與uat且uat仍在cacheMaxAge內(nèi)解析器會(huì)直接用它構(gòu)建本地選鍵器免去首次 HTTP 請(qǐng)求作為回寫目標(biāo)成功 fetch 后解析器會(huì)把新的jwks與uat寫回該對(duì)象。緩存對(duì)象的結(jié)構(gòu)為ExportedJWKSCache{ jwks: JSONWebKeySet; uat: number }其中uat是“最后更新時(shí)間”毫秒時(shí)間戳。輸入類型JWKSCacheInput允許ExportedJWKSCache或空對(duì)象{}類型別名。推薦使用模式// 前提從低延遲 KV 存儲(chǔ)拉取上次緩存 let getPreviouslyCachedJWKS!: () Promisejose.ExportedJWKSCache let storeNewJWKScache!: (cache: jose.ExportedJWKSCache) Promisevoid const jwksCache: jose.JWKSCacheInput (await getPreviouslyCachedJWKS()) || {} const { uat } jwksCache const JWKS jose.createRemoteJWKSet(url, { [jose.jwksCache]: jwksCache, }) await jose.jwtVerify(jwt, JWKS) if (uat ! jwksCache.uat) { await storeNewJWKScache(jwksCache) }核心邏輯驗(yàn)簽前先取緩存無則{}驗(yàn)簽后對(duì)比uat是否變化變化才回寫存儲(chǔ)。這避免了每次冷啟動(dòng)都重新拉取 JWKS也避免了在每次調(diào)用中把整份 JWKS 打進(jìn)存儲(chǔ)。源碼中的回寫發(fā)生在reload內(nèi)部src/jwks/remote.ts。七、customFetch自定義 fetch 實(shí)現(xiàn)customFetch同樣是unique symbolsrc/jwks/remote.ts用于把解析器內(nèi)部的 HTTP 請(qǐng)求替換成你自己的實(shí)現(xiàn)從而獲得代理、重試、日志、測(cè)試 mock 等能力。其類型FetchImplementation的簽名為type FetchImplementation ( url: string, options: { headers: Headers method: GET redirect: manual signal: AbortSignal }, ) PromiseResponse[!NOTE] 已知坑把options透傳給 ky 等 fetch 類庫時(shí)大概率遇到類型不匹配這些庫的 typings 幾乎不與原生 fetch 完全對(duì)齊建議使用ts-expect-error處理。用 ky 實(shí)現(xiàn)重試與日志import ky from ky const JWKS jose.createRemoteJWKSet(url, { [jose.customFetch]: (...args) ky(args[0], { ...args[1], hooks: { beforeRequest: [(request) { logRequest(request) }], beforeRetry: [({ request, error, retryCount }) { logRetry(request, error, retryCount) }], afterResponse: [(request, _, response) { logResponse(request, response) }], }, }), })用 undici 接入 HTTP 代理import * as undici from undici let envHttpProxyAgent new undici.EnvHttpProxyAgent() const JWKS jose.createRemoteJWKSet(url, { [jose.customFetch]: (...args) // ts-ignore undici.fetch(args[0], { ...args[1], dispatcher: envHttpProxyAgent }), })用 undici 自動(dòng)重試網(wǎng)絡(luò)錯(cuò)誤import * as undici from undici let retryAgent new undici.RetryAgent(new undici.Agent(), { statusCodes: [], errorCodes: [ ECONNRESET, ECONNREFUSED, ENOTFOUND, ENETDOWN, ENETUNREACH, EHOSTDOWN, UND_ERR_SOCKET, ], }) const JWKS jose.createRemoteJWKSet(url, { [jose.customFetch]: (...args) // ts-ignore undici.fetch(args[0], { ...args[1], dispatcher: retryAgent }), })用 undici MockAgent 在測(cè)試中模擬響應(yīng)import * as undici from undici let mockAgent new undici.MockAgent() mockAgent.disableNetConnect() const JWKS jose.createRemoteJWKSet(url, { [jose.customFetch]: (...args) // ts-ignore undici.fetch(args[0], { ...args[1], dispatcher: mockAgent }), })這一模式正是 test/jwks/remote.test.ts 所采用的測(cè)試策略整個(gè)測(cè)試套件通過 MockAgent 攔截https://as.example.com/jwks的響應(yīng)并用timekeeper凍結(jié)/撥快時(shí)間軸來驗(yàn)證冷卻與過期行為。八、錯(cuò)誤處理與多密鑰迭代遠(yuǎn)程 JWKS 場(chǎng)景最常見的錯(cuò)誤集中在 src/util/errors.ts 中定義均可通過jose.errors訪問錯(cuò)誤類code觸發(fā)條件JWKSNoMatchingKeyERR_JWKS_NO_MATCHING_KEYJWKS 中沒有任何可用密鑰匹配選鍵條件JWKSMultipleMatchingKeysERR_JWKS_MULTIPLE_MATCHING_KEYS有多個(gè)密鑰同時(shí)匹配可迭代以逐個(gè)嘗試驗(yàn)簽JWKSTimeoutERR_JWKS_TIMEOUTfetch 超時(shí)對(duì)應(yīng)timeoutDuration多密鑰匹配時(shí)的迭代驗(yàn)簽?zāi)J(rèn)策略是“恰好一個(gè)匹配”若遠(yuǎn)端 JWKS 中存在多個(gè)滿足條件的公鑰例如兩把 RSA 密鑰都未聲明algjwtVerify會(huì)拋JWKSMultipleMatchingKeys。此時(shí)可以顯式迭代該錯(cuò)誤逐一嘗試驗(yàn)簽const options { issuer: urn:example:issuer, audience: urn:example:audience, } const { payload, protectedHeader } await jose .jwtVerify(jwt, JWKS, options) .catch(async (error) { if (error instanceof jose.errors.JWKSMultipleMatchingKeys) { for await (const publicKey of error) { try { return await jose.jwtVerify(jwt, publicKey, options) } catch (innerError) { if (innerError instanceof jose.errors.JWSSignatureVerificationFailed) { continue } throw innerError } } throw new jose.errors.JWSSignatureVerificationFailed() } throw error }) console.log(protectedHeader) console.log(payload)這里error本身是異步可迭代的async iterable每次產(chǎn)出的是一個(gè)public類型的CryptoKey只有JWSSignatureVerificationFailed會(huì)被吞掉繼續(xù)嘗試下一個(gè)密鑰其余錯(cuò)誤直接向上拋出。測(cè)試 test/jwks/remote.test.ts 驗(yàn)證了這一迭代行為并確認(rèn)兩次迭代產(chǎn)生的密鑰對(duì)象是同一個(gè)內(nèi)部有 WeakSet 緩存。九、實(shí)現(xiàn)要點(diǎn)小結(jié)RemoteJWKSet 可調(diào)用函數(shù) 5 個(gè)實(shí)例成員coolingDown、fresh、reloading、reload、jwks全部由createRemoteJWKSet在 src/jwks/remote.ts 中安裝兩級(jí)節(jié)流cacheMaxAge10 分鐘默認(rèn)控制緩存新鮮度cooldownDuration30 秒默認(rèn)控制失敗重試時(shí)的 fetch 頻率timeoutDuration5 秒默認(rèn)控制單次請(qǐng)求上限無狀態(tài)運(yùn)行時(shí)用jwksCache做外部持久緩存驗(yàn)簽前后比對(duì)uat決定是否回寫高級(jí)網(wǎng)絡(luò)需求代理、重試、日志、mock通過customFetch以 symbol 選項(xiàng)注入實(shí)測(cè)行為與上述語義完全對(duì)應(yīng)見 test/jwks/remote.test.ts。贊分享網(wǎng)絡(luò)安全認(rèn)證鑒權(quán)后端【免費(fèi)下載鏈接】joseJWA, JWS, JWE, JWT, JWK, JWKS for Node.js, Browser, Cloudflare Workers, Deno, Bun, and other Web-interoperable runtimes項(xiàng)目地址https://gitcode.com/gh_mirrors/jo/jose點(diǎn)擊查看免費(fèi)下載相關(guān)推薦jose 本地 JWKS 公鑰解析createLocalJWKSet 完全指南jose 本地 JWKS 公鑰解析createLocalJWKSet 完全指南 本篇技術(shù)指南圍繞 jose 庫的 createLocalJWKSet 展開講網(wǎng)絡(luò)安全認(rèn)證鑒權(quán)后端jose 通用 JWS 驗(yàn)證動(dòng)態(tài)密鑰解析GeneralVerifyGetKey 接口全解析jose 通用 JWS 驗(yàn)證動(dòng)態(tài)密鑰解析GeneralVerifyGetKey 接口全解析 通用 JSON 序列化General JSON Serializ網(wǎng)絡(luò)安全認(rèn)證鑒權(quán)后端scalar/highlight 源碼級(jí)解析Scalar 零依賴高性能語法高亮引擎與 40 種語言語法體系scalar/highlight 源碼級(jí)解析Scalar 零依賴高性能語法高亮引擎與 40 種語言語法體系 本篇技術(shù)指南圍繞 Scalar 開源倉庫中的代碼網(wǎng)絡(luò)安全認(rèn)證鑒權(quán)后端上一篇5分鐘掌握AMD銳龍SMU調(diào)試工具釋放處理器隱藏性能的完整方案下一篇如何掌握AMD銳龍SDT調(diào)試工具從入門到精通的完整指南創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考