驗證與簽章
驗證與簽章
四個必要標頭、canonical string 組成,以及可核對的簽章範例
每個 /v1 請求都必須帶上四個標頭:X-Client-Id、X-Timestamp、X-Nonce、X-Signature。伺服器會用這四個值加上請求本身的內容重新算出一次簽章,與 X-Signature 比對,只有相符才會放行。
四個標頭
canonical string 的組成
canonical string 是簽章真正計算的對象,由五行組成,行與行之間用換行字元(\n)相接:
五行的順序固定,欄位名稱(x-timestamp:、x-nonce:、body-sha256:)也要照抄,這不是欄位說明文字,是簽章輸入的一部分。
path 一定含 query string,而且逐字不正規化
第二行的「路徑」不是只有 /v1/customers 這種乾淨路徑——只要請求帶了 query string,就要把 ? 之後的部分原封不動接在路徑後面一起簽。伺服器驗證時是把收到的路徑與 query string原樣串起來計算,不會排序參數,也不會做任何正規化:你送出什麼樣的位元組,就要簽那個樣子的位元組,順序、大小寫、百分號編碼都必須與實際送出的請求完全一致。
舉例,一個查詢客戶清單的請求:
canonical string 的第二行必須是:
要簽的是帶著 %3A 的原始字串,不是解碼後的 /v1/customers?page=1&updatedSince=2026-07-01T00:00:00Z——把 %3A 解成 : 再簽,算出來的簽章會跟伺服器重算的不一樣,直接收到 401。同理,如果你的程式在送出前把參數重新排序(例如把 updatedSince 排到 page 前面),就必須簽「重新排序後、實際送出的那個字串」,而不是原本想像中的順序——一切以「這個 HTTP 請求實際傳輸的那一行」為準。
沒有 query string 的請求(例如 POST /v1/customers)不受影響,第二行就是乾淨的路徑本身,這也是下面 fixture 範例採用的情況。
沒有 body 時,仍要算空字串的雜湊
GET 請求或任何沒有 body 的請求,第五行不能省略,也不能留空——要對「長度為 0 的空位元組序列」算 SHA-256,而不是對空字串本身做特殊處理跳過這一步。空位元組序列的 SHA-256 固定是:
所以一個沒有 body 的 GET /v1/product-categories 請求,canonical string 的最後一行會是 body-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855。
逐字核對的簽章範例
下面這組數字是用正式簽章邏輯實際跑出來的固定輸入與輸出,用來讓你核對自己那端算出來的 canonical string 與簽章是否正確。直接照抄這些值來驗證你的實作,不要自己重新湊一組。
secret 一欄是這組範例專用的測試值,本身就標明「請勿用於正式環境」——正式的 client_secret 由 OrderUp 在開通時提供,不會是這個值。
簽章函式範例
三段範例實作的是同一套演算法:先算 body 的 SHA-256,組出五行 canonical string,再用 client_secret 對它做 HMAC-SHA256 並轉成 Base64。用上面的固定輸入分別代入,應該都能算出同一個 X-Signature。
沒有 body 的請求(例如 GET),把上面函式的 body 參數傳入長度為 0 的空位元組(Node 的 Buffer.alloc(0)、C# 的 Array.Empty<byte>()、Python 的 b"")即可,不需要另外分支處理。
收到 401 時的排查清單
401 代表這個請求沒有通過驗證,回應本身不會說明是哪一項失敗(避免洩漏線索給攻擊者)。失敗的原因不只簽章一種——四個標頭少帶或帶了空值、X-Timestamp 不是純數字、X-Client-Id 不是 OrderUp 配發的值或尚未開通,得到的都是一模一樣的 401。依序檢查:
- 四個標頭沒有全部帶齊,或其中一個是空值。
X-Client-Id、X-Timestamp、X-Nonce、X-Signature缺任何一個、或值是空字串,都會在簽章比對之前就被擋下。另外X-Timestamp必須是 Unix 秒數的純數字,寫成 ISO-8601 時間字串或帶小數點的毫秒值一樣會被拒絕。 X-Client-Id打錯了,或這組憑證還沒開通。 這個值必須與 OrderUp 配發給你的完全一致(大小寫也算),而且該組憑證必須已經是啟用狀態。剛拿到憑證、還在等開通就先接上去測,看到的就是這個401——若其他項都排除了,請先向 OrderUp 確認這組X-Client-Id已經開通。- 本機時鐘沒有對齊 NTP。
X-Timestamp與伺服器時間誤差超過 ±300 秒就會被拒絕,機器上的時間飄移是最常見的原因。 X-Nonce被重複使用。 每次請求都要換一個新的隨機值;重試同一個請求時如果沿用了舊的 nonce 而不是重新產生,會被視為重放攻擊。- 簽的是正規化過的 query string,不是實際送出的那個。 常見於 HTTP 用戶端函式庫會自動幫你排序參數或把
%3A解碼成:——簽章必須用「線路上實際傳輸的那個路徑+query string」,見上面「path 一定含 query string」的說明。 - body 在簽完名之後又被序列化器動過。 例如簽章時用了一份 JSON 字串,送出時卻讓 HTTP 函式庫重新序列化物件(欄位順序、空白、跳脫字元都可能因此改變),這樣簽章對應的 body 就跟實際送出的 body 不是同一組位元組了。務必確保「拿去算
body-sha256的那份 body」與「實際放進請求裡的那份 body」逐位元組相同。 client_secret前後夾帶了空白或換行。 從設定檔或環境變數讀取時很容易多帶一個換行字元,導致 HMAC 用的 key 跟伺服器那端不一致。
排除以上七項仍然失敗,請聯繫 OrderUp,並提供發生問題那次請求的時間戳記與 nonce(請勿提供 client_secret),協助排查。