通用面試:如何安全使用 URI Template 產生 API 連結?
題幹與適用場景
團隊希望用 URI Template 統一產生 API 文件、分頁連結和批次查詢 URL,例如 /users{?status,limit}。模板來自設定檔,變數值部分來自使用者輸入。請說明 RFC 6570 的表達式層級、保留字元與清單展開規則,並設計安全的解析、展開和驗證邊界。
面試官考察點
- 是否能區分模板語法、變數值編碼與最終 URI 的解析驗證。
- 能否解釋簡單展開、保留字元展開、路徑/查詢/矩陣參數展開的差異。
- 是否處理清單、關聯陣列、explode、前綴截斷和未定義變數。
- 能否識別不可信模板帶來的 SSRF、路徑穿越、開放重新導向和敏感資訊洩漏風險。
回答前需要釐清的問題
- 模板是靜態程式碼、受審核設定,還是租戶或使用者可以提交?
- 產生結果只用於展示,還是會被伺服器 HTTP 用戶端直接請求?
- 變數是字串、清單、關聯陣列,還是允許巢狀 JSON?
- 允許哪些 scheme、host、port 和路徑前綴,是否必須限制在目前 API origin?
- 需要相容 RFC 6570 的全部層級,還是只支援審核過的操作符子集?
30 秒回答框架
我會把模板解析、變數編碼和最終 URI 策略分開。先定義支援的 RFC 6570 操作符與資料型別,再按操作符執行百分比編碼、清單和關聯陣列展開;未定義變數應按規範省略。展開後把結果當作不可信 URI,解析並驗證 scheme、host、port 和規範化路徑,禁止模板決定伺服器可存取的網路目標。測試涵蓋保留字元、Unicode、空值、重複鍵、前綴和惡意路徑。
分步驟深入解答
第一步:明確 URI Template 的邊界
URI Template 是用變數表達 URI-reference 的模板語法,不是 HTTP 用戶端、URL 白名單或業務路由器。實作必須先解析字面文字和表達式,再根據變數值產生結果;不能把模板字串直接拼接到請求中,也不能把展開結果當成已安全的 URL。
第二步:按層級實作操作符
RFC 6570 從簡單變數展開開始,逐步涵蓋保留字元、路徑片段、標籤、路徑段、矩陣參數以及查詢和查詢延續。常見操作符包括 +、#、.、/、;、? 和 &。操作符決定分隔符、空值表現和哪些保留字元可以保留,不能用一個通用字串替換規則代替。
第三步:處理值編碼與複合值
簡單字串中的保留字元應按表達式規則百分比編碼。清單可用逗號連接或透過 explode 變成重複參數;關聯陣列也有不同的鍵值分隔方式。前綴修飾符只取字串前綴長度,不能把它誤當成 Unicode 字元數、位元組數或安全截斷。實作應明確 UTF-8、空字串和未定義變數的行為。
第四步:隔離模板來源與變數來源
靜態、經程式碼審核的模板可以支援較完整的操作符;租戶設定應限制表達式、變數名和輸出元件。變數值可以來自請求,但模板不應允許呼叫函式、讀取環境變數或拼接任意 scheme。把模板 AST 與變數字典分離,避免透過變數名注入新的表達式語法。
第五步:驗證最終 URI
展開完成後用標準 URI 解析器讀取 scheme、authority、path、query 和 fragment。若結果用於伺服器請求,scheme、host、port 必須符合 allowlist,解析 DNS 後還要阻斷回環、鏈路本地和私有位址,並在重新導向時重新驗證。路徑規範化後再檢查根路徑,避免編碼後的 .. 繞過規則。
第六步:設計 API 連結的可維護性
分頁連結模板應明確哪些變數由伺服器產生,篩選條件應採用固定變數名和型別。不要把簽章、存取權杖或內部主機名放入可公開的模板或產生結果。記錄模板版本、變數 schema 和展開錯誤,結果需要穩定排序時由業務層保證,而不是依賴參數字串順序。
第七步:建立對照測試與監控
測試每個操作符的單變數、清單、關聯陣列、空值和未定義值,比較規範範例的預期結果。加入保留字元、Unicode、重複查詢鍵、過長前綴、雙重編碼、%2e%2e、不同 scheme、重新導向和 DNS 解析的安全用例。監控展開失敗率、拒絕原因、目標 origin 分布和異常長度,發現模板設定變化時觸發審核。
高品質示範回答
我會先限制支援的 RFC 6570 操作符,並把模板 AST、變數 schema、百分比編碼和最終 URI 驗證拆開。清單、關聯陣列、explode 和前綴修飾符按各自規則處理,未定義變數省略;不允許變數值重新解釋為表達式。展開結果始終視為不可信 URI,解析後只允許目前 API 的 scheme、host、port 和規範化路徑,伺服器請求還要阻斷私有位址並在每次重新導向時複檢。測試涵蓋 RFC 範例、Unicode、空值、重複鍵、雙重編碼、路徑穿越和 SSRF,記錄模板版本與拒絕原因。
常見錯誤
- 用字串拼接代替操作符語意,導致查詢分隔符或百分比編碼錯誤。
- 把
+保留字元展開當成「完全不編碼」,忽略表達式規則和 URI 元件邊界。 - 把清單、關聯陣列和 explode 當成同一種逗號連接格式。
- 只驗證模板文字,不驗證展開後的 scheme、host、port 和規範化路徑。
- 把 URL 編碼當作 SSRF 防護,允許伺服器跟隨未經複檢的重新導向。
追問及應對
追問一:未定義變數應該怎樣處理?
按表達式規則省略未定義變數及其必要分隔符,不應把它渲染成字串 null。業務層若要求必填,應在展開前依據變數 schema 報錯。
追問二:為什麼不能只呼叫 URL encode?
編碼方式由表達式和 URI 元件決定,查詢參數、路徑段和保留字元展開的分隔符不同。單一編碼函式無法決定複合值、空值、前綴和操作符行為。
追問三:模板來自租戶時如何降低風險?
使用審核後的操作符子集、固定變數 schema 和輸出元件,不允許任意 scheme 或 authority。展開後仍執行 origin allowlist、路徑規範化、DNS/IP 檢查和重新導向複檢。
追問四:前綴修飾符能用於截斷敏感字串嗎?
它是 URI Template 的字串前綴語意,不是隱私脫敏或 Unicode 安全截斷。敏感資料應在業務層顯式遮罩,長度和字元邊界也要由業務規則定義。
追問五:如何證明實作相容 RFC 6570?
執行 RFC 6570 的規範範例和各級操作符測試,比較每個變數型別的預期展開結果;再補充專案自己的安全拒絕用例,並記錄未支援的層級和差異。