Skip to main content

Lesson 11: 應用層的 Zero Trust-資料驗證 (Validation) 與 DTO

Updated 3 min readMarkdown
Part of series從Rookie到Junior,一個後端成長的30堂課

在資安領域有一個非常有名的概念叫做 Zero Trust (零信任)。它的核心精神只有一句話:"Never Trust, Always Verify" (永不信任,始終驗證)。

在網路世界裡,後端工程師的第一條守則是:「永遠不要相信客戶端傳來的輸入」。

過去工程師常認為:「只要使用者登入了(通過驗證),他傳來的資料就是安全的」或者「這是我們自己寫的前端 App 傳來的資料,所以可以信任」。這就是傳統的邊界防禦 (Perimeter Security) 思維——就像城堡,進了城門就沒人管你了。

但對於後端工程師來說,API 接口就是戰場的最前線,沒有所謂的「城牆內」。

無論前端做了多完美的檢查,惡意攻擊者依然可以用 curl 或 Postman 直接對你的 API 灌入垃圾數據或攻擊代碼。這堂課我們探討如何建立一道堅固的防火牆,確保進入我們系統核心(業務邏輯與資料庫)的數據是乾淨、合法且型別正確的

為什麼要用 Zero Trust 看待輸入資料?

將 Zero Trust 的哲學應用在資料驗證上,我們可以這樣理解:

  • 傳統思維:

    • 「前端有寫 JavaScript 檢查 Email 格式了,後端不用再檢查一次吧?」

    • 「這是內部管理後台,只有員工會用,不用防 SQL Injection 吧?」

    • 後果:一旦前端驗證被繞過(攻擊者直接用 Postman 打 API),後端完全不設防,直接讓惡意資料長驅直入。

  • 零信任思維 (Zero Trust Security):

    • 假設前端已經淪陷:我們假設發送請求的不是你的前端 App,而是一個正在嘗試注入惡意語法的駭客。

    • 身份不代表清白:即使請求帶有合法的 JWT (Token),代表他是「合法的使用者」,但不代表他帶來的資料是「無害的」。

    • 微觀邊界 :每一個 API Endpoint 都是一個獨立的檢查站。不管資料從哪裡來,進入這個函式之前,必須先經過嚴格的驗證。

DTO (Data Transfer Object)

在我的開發生涯中,曾經看過有工程師 直接把 HTTP Request 的 JSON Body 整個丟進資料庫模型(Model)裡。這是極度危險的行為(大量指派漏洞)。

DTO 模式 的核心思想是:建立一個專門的物件,用來定義「這個 API 接口預期接收什麼樣的資料」。

  • 沒有 DTO (危險):

    • 客戶端傳送 { "username": "admin", "role": "superuser" }。

    • 後端直接接收並寫入資料庫 -> 一般使用者突然變成了超級管理員。

  • 使用 DTO (安全):

    • 後端定義一個 UpdateProfileDTO,裡面只有 { "username": string }。

    • 即使客戶端多傳了 role,因為 DTO 裡沒定義,系統會自動忽略或報錯。

驗證的三個層次

一個成熟的後端架構,驗證通常發生在不同階段:

Layer 1: 結構與型別驗證 (Syntactic Validation)

這是最外層的過濾,通常由 Schema Validator 處理。如果不通過,程式根本不應該進入業務邏輯。

  • 檢查項目:

    • 欄位是否存在?(Required)

    • 資料型別對不對?(Is it a String, Integer, UUID?)

    • 格式對不對?(Email format, Date format ISO8601)

  • 通用工具:

    • Python: Pydantic

    • Node.js/TS: Zod, Joi, class-validator

    • Go: Struct Tags (binding:"required,email")

    • JSON Schema: 跨語言的標準定義

Layer 2: 語意與邏輯驗證 (Semantic Validation)

這層涉及資料的「內容」是否合理。

  • 檢查項目:

    • 數值範圍:年齡不能是負數,庫存不能小於 0。

    • 依賴關係:如果 payment_method 是信用卡,則 card_number 必填。

    • 商業規則:開始時間不能晚於結束時間。

Layer 3: 資料庫完整性驗證 (Database Integrity)

這是最後一道防線,通常涉及對資料庫的查詢。

  • 檢查項目:

    • 唯一性檢查 (Unique):這個 Email 是否已經被註冊過?

    • 關聯性檢查 (Foreign Key):這個 product_id 是否真的存在於商品表中?

💡
在Laravel中,通常會封裝Form Request來解決Layer1 + Layer2

Fail Fast 原則

後端驗證的核心哲學是 Fail Fast (快速失敗)。 一旦發現資料不合法,立刻回傳 400 Bad Request 或 422 Unprocessable Entity,不要讓錯誤的資料繼續往後跑,浪費運算資源或導致更深層的錯誤(例如資料庫報錯)。

補充

關於DTO

DTO , Data transfer(to) Object。本質是「資料傳輸的載體」,它不只用來「收(Input)」資料,也非常適合用來「發(Output)」資料。

我們上面談了 DTO 如何像盾牌一樣防禦惡意輸入 (Input DTO),但 DTO 還有另一個強大的功能:規範輸出的形狀 (Output DTO / Response DTO)。

如果我們直接把資料庫撈出來的 Model 直接 return 給前端或第三方。這會導致兩個問題:

  1. 洩漏敏感資料:不小心把 password_hash、soft_delete_flag 或內部 id 傳出去。

  2. 格式不符需求:資料庫存的是 created_at (Timestamp),但對方要的是 XML 格式的 <publishedDate>。

💡
Laravel 在Model的地方可以設定 $hidden 來解決問題1。可以用transform(on Model Collection)來解決問題2。

實戰場景:整個電商網站商品 XML Feed

假設你需要生成一個 XML 檔案給 Google Merchant Center 或比價網爬蟲使用。你的資料庫結構可能很複雜,但對方要求的格式很死板。這時,你可以用 DTO 來做「結構化映射」。

情境:

  • 資料庫 (DB):products 表,欄位有 id, name, cost_price, sale_price, stock_qty, supplier_id。

  • 需求 (XML):需要 <item><title>...</title><price>...</price></item>。

使用 Output DTO 的思維:

// 這是我們定義的「模具」,不管資料庫怎麼變,輸出的格式永遠長這樣
class ProductXmlDTO
{
    public string $title;
    public float $price;
    public string $availability;

    // 透過建構子或工廠方法,把 DB Model 轉換成 DTO
    public static function fromModel(Product $product): self
    {
        $dto = new self();
        // 1. 改名 (Mapping):DB 的 name 對應 XML 的 title
        $dto->title = $product->name;

        // 2. 邏輯轉換 (Transformation):DB 存的是成本與售價,這裡只輸出最終售價
        $dto->price = $product->sale_price;

        // 3. 狀態判斷 (Logic):將數字庫存轉為文字描述
        $dto->availability = $product->stock_qty > 0 ? 'in stock' : 'out of stock';

        return $dto;
    }
}

這樣做的好處

  1. 解耦 (Decoupling):如果有一天你的資料庫欄位 name 改成了 product_name,你只需要修改 DTO 的 fromModel 方法,外面的 XML 輸出完全不會壞掉。

  2. 單一資料來源 (SSOT):你可以針對不同的需求製作不同的 DTO(例如 ProductCardDTO 給手機版列表用,ProductDetailDTO 給詳情頁用),而不是讓前端自己去撈一堆不需要的欄位。

大量指派漏洞(Mass Assignment Vulnerability)

這裡我們來談談什麼是大量指派漏洞,用一個具體情境來說明:

  1. 你有一個「更新使用者資料」的功能,讓使用者編輯自己的個人資訊(profile)。

  2. 你提供一個 PATCH/PUT API 供前端送出更新請求。

  3. 在 users table 中,你有一個 remember_token 欄位,用於實現「持久登入」。

  4. 客戶端在呼叫更新 API 時,body 中不小心也包含了 remember_token。

  5. 後端程式如果直接使用 $request->all() 去更新資料模型(User::update($request->all())),那麼 remember_token 就會在你完全未察覺的情況下被改寫。

這就是典型的 大量指派漏洞:

攻擊者或不小心的客戶端,只要傳入一個你沒預期要更新的欄位,就能成功覆寫你的資料。

💡
Laravel 的 $fillable 與 $guarded ,但只能做欄位白名單/黑名單的基本防護,無法取代真正的資料驗證流程。

About the author

  • 一個很忙的工程師,比起寫code更喜歡股票,QQQ買起來,謝謝!

No comments yet. Be the first to comment.

從Rookie到Junior,一個後端成長的30堂課

Part 14 of 31

“從 Rookie 到 Junior:一個後端成長的 30 堂課” 是一套專為後端新手所設計的成長型技術系列文章。內容以實務為導向,逐步拆解後端工程的核心能力,包括程式語言基礎、架構思維、框架運作原理、業務邏輯設計、資料庫操作、以及常見的開發模式。 本系列的目標是協助讀者從零散的學習堆疊,建立成體系的後端知識框架。讀者能夠理解各語言背後不變的工程思維與設計原則。這套內容旨在讓學習者從「會寫程式」進入「能理解系統設計」的階段,逐步具備勝任 Junior Backend Engineer 的能力。

Up nextLesson 12: 隱形的翻譯官-Middleware 中介層的應用場景想像今天要進入一個戒備森嚴的皇宮(核心商業邏輯 Controller/Service): 大門警衛:檢查你有沒有通行證(Authentication)。 安檢人員:檢查你有沒有攜帶危險物品(Input Validation/Sanitization)。 禮儀官:看你是哪國人,幫你掛上對應語言的翻譯機(Localization)。 記錄員:記下幾點幾分誰進去了(Logging)。 這四個角色,就是 Middleware。它們不負責「處理皇宮內的政務」(那是 Controller 的事)...

More from this blog