昨天我們確保了「操作發生的順序」不會出錯——檢查跟扣除庫存綁成同一個原子操作,不怕被插隊。但操作順序再正確,如果送進來的資料本身就是錯的、甚至是惡意的,系統一樣會壞。今天要談的資料驗證,處理的正是這一層:跨越客戶端、傳輸層和伺服器層的多層安全合約。在零信任原則下運作的現代網頁系統裡,每個邊界都需要明確的驗證,把驗證當成一種結構性的設計,而不是想到才加的開發習慣。
輸入驗證確保輸入系統(如網頁表單或應用程式)的資料符合預定義的標準,例如格式、型別和範圍。它是抵禦惡意輸入和錯誤資料的第一道防線,這些輸入和資料可能會導致 SQL 隱碼攻擊或跨網站指令碼(XSS)等漏洞。
前端驗證專注於使用者體驗和互動層控制。它提供即時回饋和結構化格式強制執行(例如驗證必填欄位、字串長度及正規表示式),以便在傳輸前攔截格式錯誤的資料並減少不必要的伺服器負載。然而,由於客戶端控制可以透過瀏覽器開發人員工具或直接呼叫 API 來繞過,前端驗證僅作為指導方針,不能當成安全性邊界。
型別檢查確保每個欄位中的實際值能被解析為預期的資料型別(例如字串、整數、布林值或列舉)。在前端和後端之間實作共用的綱要定義(使用 Zod 或 TypeScript 等工具),可以防止驗證規則漂移,並為資料結構建立可靠的合約:
// 用 Zod 定義訂單的型別邊界,前後端共用同一份規則
const OrderSchema = z.object({
price: z.number().positive(), // 必須是正數
quantity: z.number().int().positive(), // 必須是正整數
currency: z.enum(['USD', 'JPY', 'KRW']) // 只能是這三種幣別
});
// 後端收到請求後,第一件事就是驗證
const result = OrderSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ error: '資料格式不正確' });
}
防禦性程式設計意味著預期每個使用者輸入都可能產生最壞的結果,並在信任邊界將其視為不受信任的資料。透過強制執行範圍檢查、一致性檢查和跨欄位邏輯,系統可以防止連鎖失敗並維持下游的資料完整性。
以代購 App 為例,當代購商在介面上新增商品或記錄訂單時,若僅依賴前端檢查金額與數量,惡意使用者或異常請求可能會繞過 UI,直接呼叫 API 傳送負數金額、文字型別的數量,或非預期的幣別格式,導致後端計算錯誤或資料庫損毀。強固的縱深防禦架構會透過前端即時提示輸入格式,並在後端與資料庫建立嚴格的型別邊界與條件約束(例如確保價格欄位大於 0、數量為整數,以及關聯鍵的完整性),確保每一筆訂單與代購交易在所有層級中的資料一致性與安全性。
有效的資料驗證需要策略性的關注點分離:前端保持資訊豐富以最佳化使用者體驗,後端和資料庫則作為不妥協的受信任邊界,強制執行安全性和完整性。昨天處理的是「順序不能亂」,今天處理的是「內容不能假」——兩者合起來,才是一個系統面對真實世界時該有的基本防護。把「零信任」這個原則具體化之後,才明白為什麼前端驗證做得再完善,後端還是得重新驗證一次——不是不信任前端工程師,而是介面之外還有太多繞過畫面直接打 API 的可能性,這道防線不能省。