我的一個 Laravel 專案的許可證啟動端點開始拒絕它所傳遞的所有令牌。不是某些令牌。每個令牌,包括同一伺服器九十秒前發布的令牌。日誌說 token expired. 代幣沒有過期。週六的大部分時間我都確信盒子上的時鐘已經漂移了。
它有't。錯誤是一行:
if (payload.exp < Date.now()) throw new Error('token expired')
exp 在 JWT 中是 秒 自時代以來 - RFC 7519 §4.1。4 對此是明確的。 Date.now() 在 JavaScript 中傳回 毫秒. 所以我比較了十位數和十三位數,十位數總是更小。宇宙中的每個令牌都永遠過期了。修復是 Date.now() / 1000。 診斷花了八個小時,因為我實際上從來沒有 看了 在令牌上 - 我不斷重新閱讀我自己的程式碼,這相當於在佩戴眼鏡時搜尋眼鏡的調試。
最終打破循環的是將令牌貼到解碼器中,讀取 exp: 1748952000,將其轉換為日期,並在未來兩週看到時間戳記。代幣很好。我的比較錯了。查看資料三十秒比查看程式碼八小時。
那'這就是本指南的內容。不聰明的調試理念 - 我將特定的、無聊的、乏味的瀏覽器工具保存在一個名為 "API 調試和引用的選項卡組中;每個的實際用途以及它們捕獲的故障模式。這裡的一切都在客戶端運行 工具。dev,這比聽起來更重要,當你和#39;要貼上的是生產承載令牌。
TL;DR: 當 API 行為不當時,停止讀取程式碼並開始讀取有效負載。將回應格式化為 json 格式化程式。 用 破解令牌 中國航空解碼器,不是通用的 Base64 工具。轉動
exp,iat, 和X-RateLimit-Reset與真實約會 時間戳轉換器。 將工作響應與損壞響應進行比較 文字差異 或者,更好的是, JSON 差異. 解碼查詢字串損壞 網址編碼器. 所有這些都在您的瀏覽器中運行 - 令牌永遠不會越線。
為什麼調試 API 感覺比調試程式碼更糟?
因為你可以't 穿過它。本地錯誤有堆疊追蹤、偵錯器和斷點。 API 錯誤有一個字串。其他人'伺服器根據您只知道一半的規則產生該字串,並且您的工作是從該字串向後工作。
這顛倒了通常的技能。瓶頸是't邏輯,it's 易讀性. 幾乎每個 API bug I'過去幾年中追逐的都是看不見的,直到我使資料可讀:
- 事實證明,JSON 的 4,000 個字元的縮小回應已經完成
"data": null埋在六號深處。 - Base64 有效負載解碼為錯誤訊息,API 太禮貌了,無法放入狀態代碼。
- 我的 HTTP 用戶端正在幫助添加一個網路掛鉤,該網路掛鉤未能通過簽名驗證,因為該主體有一條尾隨換行符。
- 當文件說秒時,時間戳以毫秒為單位。 (兩次。不同的公司。)
這些都不是難題。他們都是 難以讀懂 問題。以下工具的存在是為了使數據足夠快地清晰可見,以便您注意到眼睛會滑過的東西。
哪種工具可以治療哪種症狀?
這是我希望五年前有人遞給我的桌子。症狀在左邊,第一步在右邊。
| 症狀 | What'通常是真的 | 第一步 |
|---|---|---|
| 響應是一條巨線,can't 看到結構 | 沒什麼'壞了,它'剛剛縮小 | json 格式化程式 |
401/403 在你剛剛鑄造的代幣上 |
時鐘、聲明或比較錯誤 | 中國航空解碼器 → 檢查 exp, aud, iss |
| 日期顯示為 1970 年或年份 56122 | 秒/毫秒不符 | 時間戳轉換器 |
| &引用;昨天有效&引用; | 一塊田地變形了 | JSON 差異 新舊響應 |
| 帕拉姆到達時已損壞或截斷 | 雙重編碼,或未轉義的 &/+ |
網址編碼器 |
| Webhook 簽章永遠不會匹配 | 主體位元組與 you're 哈希不同 | 哈希產生器 在確切的原始身體上 |
Authorization: Basic ... 拒絕 |
憑證編碼錯誤,或空間雜散 | Base64 轉換器 |
| 配置驅動的部署失敗,API 甚至無法運作 | YAML 縮排 | yaml 驗證器 |
| 兩個反應看起來相同,但行為不同 | 看不見的性格 | 文字差異 |
下面的所有內容都是該表的長版本。
如何讓 API 回應在十秒鐘內可讀?
貼到裡面 json 格式化程式. 那'是整個技術,而 I'我並不油嘴滑舌--我唯一的最高槓桿調試習慣就是拒絕 原因 我擁有的有效負載't 格式化。
這裡'是我從計費提供者那裡得到的回應形狀,就像它從電線上得到的那樣:
{"subscriptions":[{"id":"sub_7f3d8a2b","status":"active","plan":{"id":"pro_annual","interval":"year","amount":9900},"current_period_end":1748952000,"cancel_at_period_end":false}],"has_more":false}
格式化後,它'是一個完全不同的物件 - 不是解析器,而是我:
{
"subscriptions": [
{
"id": "sub_7f3d8a2b",
"status": "active",
"plan": {
"id": "pro_annual",
"interval": "year",
"amount": 9900
},
"current_period_end": 1748952000,
"cancel_at_period_end": false
}
],
"has_more": false
}
現在我可以看到這一點 amount 是 9900 並且不是 99.00- it'以美分為單位,這是支付中最常見的整合錯誤 - 等等 current_period_end 是一個十位數的整數,意思是秒,意思是 don't 把它交給 new Date() 直接。
驗證捕捉您的眼睛贏得的東西't
格式化也會驗證。解析失敗是資訊。實際有效負載中實際出現的錯誤:
- 尾隨逗號。 在 JavaScript 中合法,在 JSON 中非法(per RFC 8259).手工編輯的固定裝置充滿了它們。
- 單引號。 JSON 需要雙引號。 Python's
str(dict)輸出不是 JSON,無論它看起來有多像。 - 未引用的密鑰。 同樣的故事 - that'是 JavaScript 物件的文字,而不是 JSON。
NaN/Infinity. 一些序列化器發出它們。 JSON 沒有這樣的文字。- 一個炸彈。 前面有一個 UTF-8 位元組順序標記
{將使嚴格的解析器拒絕在螢幕上看起來完美的文檔。
如果您的 JSON 有效,但是 形狀 是錯的,那'是一個不同的工具 - 請參閱下面的差異部分。
為什麼使用 JWT 解碼器而不是僅使用 Base64 解碼令牌?
您可以用手 Base64 解碼 JWT。我這樣做了很多年。它'是一個壞習慣,這裡'這是為什麼。
JWT (RFC 7519) 是由點分隔的三個區塊:標頭、有效負載、簽名。每個區塊都是 基地64url,不是標準 Base64 - RFC 4648 §5,交換的 URL 安全字母表 +→- 和 /→_ 並且通常會掉落 = 填充。將其輸入到嚴格的標準 Base64 解碼器中,它要么會出錯,要么會默默地給你垃圾位元組。因此,手法意味著每次都會在點上分割、重新填充和交換字母表,然後用眼球觀察原始 JSON。
的 中國航空解碼器 將所有這些都粘貼在一張紙上,並且 - 實際上節省時間的部分 - 將聲明呈現為聲明。我檢查的那些,依序:
exp(呼氣)和iat(發佈於)- 兩者 數字日期,即 秒 自 1970 年 1 月 1 日起 UTC。這是我星期六吃的場地。aud(觀眾)- 為您的舞台 API 鑄造的代幣在結構上將是完美的,但仍然會被產品拒絕。iss(發行人)-在身分提供者遷移之後,這個欄位悄悄改變了。alg在標題中 - 如果它說none,您有安全問題,而不是調試問題。
以規範的例子標記每個人's 看到:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
標題: {"alg":"HS256","typ":"JWT"}. 有效負載: {"sub":"1234567890","name":"John Doe","iat":1516239022}.和 iat 有 2018-01-18T01:30:22Z- 您只能透過時間戳轉換器來了解這一點,這就是下一部分的全部要點。
解碼器不做的事情
解碼不驗證。任何人都可以解碼 JWT;有效負載是編碼的,而不是加密的。解碼器告訴您令牌是什麼 索賠永遠不要知道該說法是否屬實。簽名驗證會在您的伺服器上使用您的秘密進行,並且任何瀏覽器工具都不應該傳遞該秘密。像對待您一樣對待解碼後的 JWT'd 將表格提交視為陌生人的斷言。
如何停止取得錯誤的時間戳記?
學習閱讀數字計數。這是整個領域最便宜的調試技巧,需要一分鐘才能學會。
| 數字 | 單位 | 例子 | new Date(x) JS給了你 |
|---|---|---|---|
| 10 | 秒 | 1748952000 |
1970-01-21 - 顯然錯了 |
| 13 | 毫秒 | 1748952000000 |
正確的日期 |
| 16 | 微秒 | 1748952000000000 |
胡說八道 |
十位數表示秒。十三表示毫秒。 JavaScript's Date 建構函式需要毫秒; Unix、Python's time.time(),去's Unix()PHP's time(),以及大多數 API' exp 田野會說話。這種不匹配的下游一切都是混亂的。
混亂在一個方向響亮,而在另一個方向則沉默。餵食 秒 對於毫秒解析器,您會得到 1970 - 這個錯誤非常明顯,您只需一分鐘即可修復它。飼料 毫秒 到a 秒 解析器,你得到年份 56122. 我查了一下: 1708876200000,解釋為秒,於 56122 年 2 月 17 日著陸。That'悄悄地傳達的方向,因為沒有任何東西拋出--訂閱永遠不會過期,四分之一的時間裡沒有人注意到。
的 時間戳轉換器 存在是為了讓你可以用一種糊狀物來解決這個問題,而不是爭論它。貼上 1748952000,閱讀日期,繼續。貼上 X-RateLimit-Reset 標頭是您的 API 正在抱怨的,並發現您有四分鐘而不是四個小時需要等待。如果時間戳很天真(否) Z(,沒有偏移)並且您需要推理它在另一個區域的含義 時區轉換器 是後續。
還有兩個值得知道的時間戳陷阱
2038 年的問題是真實存在的,而且已經過時了。 有符號的 32 位元秒計數器溢位為 2147483647,這是 2038-01-19T03:14:07Z. 任何仍在有符號 32 位元 int 中儲存時間的系統(嵌入式和遺留資料庫列中的時間比任何人都願意承認的要多)都會中斷。如果您'今天設定長期到期,您已經能夠達到此目的。
天真的時間戳記是一個遺漏的謊言。 2026-02-25T14:30:00 沒有尾隨 Z 並且沒有 +05:30 不是某個時刻;它'是未指定位置的一個時刻。我將任何傳回樸素時間戳記的 API 視為等待提交的錯誤報告。更喜歡 RFC 3339 (2026-02-25T14:30:00Z),這是網路實際使用的 ISO 8601 的嚴格、明確的設定檔。
當我引用時我該怎麼辦;昨天有效並引用;?
差異它。 Don't 理論化 - 差異化。
捕捉工作環境的反應(或從日誌中挖出最後一個好的反應)和損壞的反應,並將它們並排放置。十分之九的差異恰好是其中之一,它'五秒鐘內盯著你看。
對於 JSON,請伸手去拿 JSON 差異 在文字差異之前。它解析兩面並進行比較 結構,這意味著重新排序的鍵和不同的縮排 don't 顯示為更改 - 只有真實的更改才會顯示。以不同密鑰順序序列化的伺服器的兩個 JSON 文件的文字差異會像聖誕樹一樣亮起,並且不會告訴您任何內容。
對於 't JSON - 標頭、原始主體、設定檔、 curl 輸出-使用 文字差異。 它的特點是你的眼睛在身體上無法捕捉到的變化類別:一個尾隨空格,一個變成四個空格的選項卡,一個從Windows 機器中偷偷溜進的CRLF 行結尾,一個文檔網站用一個捲曲的引號代替了一個直接的引號。當你複製這個例子時。 I'已經寫了一整篇文章了 為什麼目光敏銳的文字比較失敗,因為有一次我花了支持升級。
為什麼我的查詢參數不斷損壞?
因為 URL 編碼有三到四種截然不同的風格,每個人's stack 選擇不同的。
經典,大致按頻率排列'咬了我:
+與%20. 在查詢字串中,+歷史上意味著一個空間(theapplication/x-www-form-urlencoded約定)。在路徑段中,+指字面加號。所以包含 base64 簽名+,未編碼地放入查詢字串中,到達時有空格,簽名檢查失敗。這確實是一個令人討厭的問題,因為這個值 看起來 就在日誌中。- 雙編碼。
%2F成為%252F因為堆疊的兩層都有助於對其進行編碼。症狀是一個參數,每次通過代理程式時都會獲得百分比符號。 - 一個原始的
&值內。 將參數一分為二。現在name=Ben & Jerry是name=Ben加上一個神秘參數叫Jerry.
將 URL 貼到 網址編碼器 並對其進行解碼。如果解碼一次仍然留下百分比逃逸,you'已經找到了你的雙重編碼。那'是整個診斷。
如何偵錯贏得並#39;t 驗證的網路掛鉤?
這是區分"的那個。我理解 HTTP"來自"I'一直在值班。&引用;
幾乎所有 Webhook 提供者都使用 HMAC 對有效負載進行簽名,並將結果放入標頭中。您的工作是計算相同的 HMAC 並進行比較。當它不匹配時't匹配,簽名幾乎從來都不是問題。 位元組是問題所在。 你沒有對他們散列的內容進行散列。
通常的嫌疑犯:
- 您對解析和重新序列化的正文進行了雜湊處理。 您的框架將 JSON 解析為一個對象,您呼叫了該對象
JSON.stringify()上面,現在鍵順序或空格因一個字元而異。你必須散列 生的 請求正文,收到的位元組。在 Express 中,這意味著之前捕獲原始緩衝區express.json()抓住它;在拉拉維爾,這意味著$request->getContent(),不$request->all(). - 尾隨換行符。 有些客戶附加一個。提供者沒有't。
- You'重新對十六進位編碼的字串而不是原始位元組進行雜湊處理,或將十六進位與基數 64 進行比較。
- 夏爾塞特。 身體具有多位元組字符,並且沿途有一些東西對其進行轉碼。
的 哈希產生器 我就是這樣隔離這個的:取得確切的主體字串,對其進行哈希處理,並與我的程式碼產生的內容進行比較 思想 是同一個身體。如果這兩個雜湊值不同,我的程式碼看不到我認為的位元組'看到,問題根本不是加密。 (也值得一提的是:如果提供者仍然提供 MD5 或 SHA-1 簽名,則 ' 是有關其平台年齡的信號。SHA-256 現在是樓層。)
調試標籤組中還存在什麼?
配角--不那麼迷人,仍然贏得了它的位置:
- uuid 生成器- 為了乾淨
X-Request-ID在每次測試呼叫中,您可以將其覆蓋到三個服務中'之後日誌。值得知道 UUID 進行了規格更新: RFC 9562 (2024) 已過時 RFC 4122 並標準化 UUIDv7,這是按時間順序排列的,因此對您的資料庫非常友善' B 樹索引比隨機 v4。如果您'今天正在為新表選擇 ID 方案,即 '是要閱讀的方案。那裡'是a UUID 版本的分解時間更長 如果你想要的話。 - Base64 轉換器- 對於
Authorization: Basic標頭(RFC 7617:it'sbase64(user:password),是的,that's 編碼,而不是安全性 - TLS 是保護它的),對於內聯二進位 blob,一些 API 會填入 JSON 欄位。 Base64 會花費約 33% 的大小開銷,這就是為什麼 "意外地大&引用;有效負載通常只有一個文件。這 完整的 Base64 指南 涵蓋了使人們絆倒的 base64url 差異。 - yaml 驗證器- 因為去年我一半的 API 故障是 't API 故障。它們是 CI 配置中的兩個空間縮排錯誤,並且根本從未部署過端點。 (不過,YAML 的故障模式比損壞的建置更嚴重:有效的 YAML 這意味著你沒有做的事情'不是故意的。我寫下了 為什麼
version: 1.10變為1.1 在我花了部署費用之後。) - 正規表示式測試儀- 目前您需要從 900 行日誌中提取一個請求 ID,其中包含您的模式#39;對此沒有信心。
- CSV 檢視器- 對於出口端點,在有人將其進口到生產之前,您需要對其輸出進行健全性檢查。
這些在瀏覽器中運行實際上重要嗎?
是的,即使我已經建立了網站,我也會這麼說。
想想你貼到調試工具中的內容。 JWT - 這是一個 即時憑證 直到它到期。生產 API 回應,即客戶資料:姓名、電子郵件、訂閱狀態。網路掛鉤正文,可能包含付款記錄。 A curl 命令與 Authorization 標題還在裡面。
現在考慮一下,根據定義,伺服器端工具會接收所有這些。不是惡意的 - 只是架構上的。該貼上進入 HTTP 請求,擊中某人'後端,並落在他們碰巧運行的任何日誌中。即使是一個一絲不苟、誠實的操作員最終也會將您的承載令牌放入他們從未打算保留的存取日誌中。
toolz。dev 上的工具在您的標籤中用 JavaScript 做工作。什麼都沒有上傳,因為那裡'無處可上傳 - 解析、解碼、雜湊都發生在您的電腦上。你不知道'也不必相信我的話:打開 DevTools,轉到網路選項卡,貼上令牌,然後注意一個永遠不會出現的請求。 That'是三十秒的審核,你應該繼續運行它 任何 你把秘密貼進的工具,包括我的。我寫下了 如何正確驗證客戶端工具 正是因為這個原因。
如果您的組織處理歐盟個人數據,這是 '不僅是衛生 - 將客戶記錄貼上到第三方伺服器中是一項處理活動,這意味著所有 GDPR 文件。客戶端工具根本不會成為處理器,從而迴避了這個問題。
實際上堅持的工作流程
六個步驟,按照我在發生某事時運行它們的順序's著火了:
- 捕獲原始響應。 全身、完整標頭、狀態代碼。不是您的應用程式'對其的解釋 - 實際位元組。
curl -i或網路標籤's"複製為curl。" - 格式化它。 json 格式化程式.看一下 形狀 在查看值之前。您需要的欄位是否存在?
- 解碼每個不透明的字串。 代幣通過 中國航空解碼器,base64 斑點通過 Base64 轉換器,透過損壞的 URL 網址編碼器. 不透明的字串經常隱藏答案。
- 將每個可能是時間的數字變成日期。 時間戳轉換器.先數數字。
- 與已知的良好反應不同。 JSON 差異. 如果您沒有已知的良好回應,這是您開始保存它們的標誌。
- 現在才去閱讀你的程式碼。 此時,您通常在開啟檔案之前知道該行。
訂單很重要。第 6 步是我以前開始的地方,它'這就是為什麼那個星期六花了八個小時。
常見問題
用於調試 API 的最佳免費工具是什麼?
對於日常 API 調試,您需要五件事:JSON 格式化程式和驗證器、JWT 解碼器、Unix 時間戳轉換器、差異工具和 URL 編碼器/解碼器。所有五個都在 toolz。dev 上免費,並且完全在瀏覽器中運行。如果您使用簽署的 Webhook,請新增雜湊產生器;如果您的部署是配置驅動的,請新增 YAML 驗證器。
將 JWT 或 API 回應貼上到線上工具中安全嗎?
僅當工具是客戶端時才可以。 JWT 是即時憑證,API 回應通常是客戶數據,因此伺服器端工具意味著將兩者都傳送給陌生人'後端。 toolz。dev 工具使用 JavaScript 處理瀏覽器中的所有內容,並且不會透過網路發送任何內容 - 透過開啟 DevTools' 親自驗證這一點;貼上時網路選項卡。對您使用敏感資料的任何工具執行相同的檢查。
為什麼我的 JWT 說 "過期&引用;當我剛剛生成它時?
最常見的原因是單位不匹配。這 exp 索賠以秒為單位(RFC 7519 將其定義為 NumericDate),但 JavaScript's Date.now() 傳回毫秒,因此直接比較它們會使每個令牌看起來都過期。解碼令牌,讀取 exp,使用時間戳轉換器將其轉換為真實日期,並在觸控驗證碼之前檢查它是否確實過去。
如何判斷 Unix 時間戳是秒還是毫秒?
數數字。十位數字是秒,十三位是毫秒,十六位是微秒。如果日期出現在 1970 年,您將幾秒鐘提供給毫秒解析器;如果它出現在 56122 年,您將幾毫秒提供給秒解析器。第二個錯誤更危險,因為沒有任何錯誤。
我可以使用這些工具來偵錯 GraphQL API 嗎?
是的。 GraphQL 回應是 JSON,因此 JSON 格式化程式和 JSON 差異的工作方式保持不變,並且 GraphQL 通常使用相同的承載令牌身份驗證 you'd 使用 JWT 解碼器進行解碼。唯一真正的區別是 GraphQL 傳回 HTTP 200 errors 數組而不是非 2xx 狀態,因此始終格式化主體 - 故障發生在有效負載內部,而不是狀態代碼中。
為什麼我的網路掛鉤簽名驗證總是失敗?
幾乎總是因為您對位元組的雜湊處理與提供者不同。如果您的框架解析了 JSON 主體並在雜湊之前重新序列化它,則空格或鍵順序已更改,HMAC 永遠不會匹配。將原始請求正文與收到的完全相同,並檢查 HTTP 用戶端新增的追蹤換行符。
Base64 和 base64url 編碼有什麼不同?
標準 Base64 (RFC 4648 §4) 使用 + 和 / 在其字母表和墊中 =. base64url (RFC 4648 §5) 取代了那些 - 和 _ 通常會丟棄填充,因此將該值安全地放入 URL 或 JWT 中。將 base64url 資料饋送到嚴格的標準 Base64 解碼器會產生錯誤或垃圾,這就是為什麼專用 JWT 解碼器會擊敗手動解碼令牌的原因。
如何調試 401 未經授權的回應?
從令牌向外工作。解碼並檢查 exp 相對於當前時間 - 過期的令牌是最常見的原因,並且在將聲明轉換為可讀日期之前它是不可見的。如果令牌是有效的,請檢查 Authorization 標頭本身:該方案必須正確存在且拼寫正確 (Bearer <token>(一個空格,沒有引號),從終端貼上的令牌通常帶有打破匹配的尾隨換行符。之後,確認 aud 和 iss 聲明與 API 的期望相符,因為為不同受眾發布的有效令牌被拒絕就像壞令牌一樣。然後才開始懷疑伺服器。
如果沒有庫,如何解碼 JWT?
JWT 是三個由點連接的基本 64url 段。在點上分割,然後以 64url 為基數解碼前兩個 - 標頭和有效負載 - 並且兩者都顯示為普通 JSON。第三段是簽名,它不會解碼為任何可讀的內容,因為它是原始位元組而不是文字。這比聽起來更重要:解碼令牌告訴您它聲稱的內容,而不是這些聲明是否正確。驗證簽名需要發行人'金鑰並且屬於您的伺服器程式碼,而不是瀏覽器工具。在此處閱讀令牌進行調試;在應用程式中驗證它們。
如果我使用這些工具,我還需要郵差還是失眠?
是的 - 他們解決了不同的問題。 API 用戶端發送請求;這些工具使回應清晰可見。在實踐中,我使用客戶端觸發請求並複製原始輸出,然後移動到瀏覽器工具來格式化、解碼、轉換和擴散它。它們在工作流程中彼此相鄰,而不是相互替換。



