Laravel 門神不好當?從自訂驗證到 Middleware,打造滴水不漏的 API 防線

2025/09/12 | Laravel技術分享

Laravel 門神不好當?從自訂驗證到 Middleware,打造滴水不漏的 API 防線

Hey,我是浪花科技的資深工程師 Eric。在寫過大大小小的 Laravel 專案後,我發現很多新手、甚至一些有經驗的開發者,常常會把所有的商業邏輯、資料驗證、權限檢查全部塞在 Controller 裡面。結果就是 Controller 變得又肥又長,幾個月後回來看,連自己都看不懂在寫什麼,簡直是技術債的溫床。老實說,這真的會讓我很頭痛。

其實,Laravel 早就為我們準備了兩大神器來解決這個問題:驗證 (Validation)中介層 (Middleware)。它們就像是你應用程式的兩位門神,一位負責檢查訪客的「證件」是否合規(資料驗證),另一位則負責檢查訪客是否有「權限」進入特定區域(請求過濾)。今天,我就來帶大家深入這兩位門神的內心世界,從基礎觀念到打造專屬的自訂規則與 Middleware,徹底解放你那可憐的 Controller!

Laravel 驗證 (Validation):不只是防君子,更是防駭客

很多開發者會覺得:「我前端不是已經用 JavaScript 做了驗證嗎?為什麼後端還要再做一次?」問得好,這就是我們今天要破解的第一個迷思。

前後端分離的迷思:為什麼後端驗證是你的最後一道防線?

身為一個有點龜毛的工程師,我必須再三強調:千萬、絕對、不要相信任何從客戶端(前端)來的資料!前端驗證的主要目的是為了提供更好的使用者體驗(UX),例如即時提示使用者Email格式錯誤,而不是為了「安全」。

為什麼?因為任何懂一點開發工具的人,都可以輕易地繞過你的前端 JavaScript 驗證,直接對你的 API 發送惡意請求。他們可以送來格式錯誤的資料、意圖注入的 SQL 語法、或是超乎預期的巨量資料。如果你的後端沒有一道堅實的防線,那你的資料庫和應用程式就等於門戶大開,等著被玩壞。

  • 前端驗證:為了 UX,是「建議」,可以被繞過。
  • 後端驗證:為了安全與資料完整性,是「規則」,不可逾越。

所以,把後端驗證當成你應用程式的最後一道、也是最重要的一道防線,絕對是必要的 paranoid(偏執)。

活用 Laravel 驗證規則,寫出優雅的防禦工事

Laravel 最優雅的驗證方式,我個人首推 Form Request。它能讓你將特定請求的驗證邏輯從 Controller 中抽離出來,變成一個獨立的類別,不僅讓 Controller 乾淨,也讓驗證邏輯可以重複使用。

假設我們要建立一個新增文章的 API,你可以用 Artisan 指令建立一個 Form Request:

php artisan make:request StorePostRequest

然後在產生的 app/Http/Requests/StorePostRequest.php 檔案中定義你的規則:

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class StorePostRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        // 在這裡可以做權限檢查,例如檢查使用者是否能發布文章
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'title' => 'required|string|max:255|unique:posts,title',
            'content' => 'required|string',
            'category_id' => 'required|integer|exists:categories,id',
        ];
    }
}

看看 `rules` 方法,我們定義了:標題(title)必填、要是字串、最多255字,且在 `posts` 資料表中必須是唯一的。分類 ID (category_id) 則必須存在於 `categories` 資料表中。是不是非常語義化且清晰?

當內建規則不夠用:打造你的專屬驗證規則 (Custom Validation Rule)

有時候,業務邏輯會比 `required` 或 `max:255` 複雜得多。例如,你需要驗證一個欄位是否為有效的台灣身分證字號。這時候,內建規則就不夠用了,我們需要打造自己的驗證規則。

同樣,用 Artisan 指令來幫我們:

php artisan make:rule IsValidTaiwanId

Laravel 會在 app/Rules 資料夾下產生一個 `IsValidTaiwanId.php` 檔案。我們需要實作 `passes` 和 `message` 這兩個方法:

<?php

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;

class IsValidTaiwanId implements Rule
{
    /**
     * Determine if the validation rule passes.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @return bool
     */
    public function passes($attribute, $value)
    {
        // 這邊只是範例,實際的身分證驗證演算法會更複雜
        $value = strtoupper($value);
        if (!preg_match('/^[A-Z][12]\d{8}$/', $value)) {
            return false;
        }
        // ... 實作完整的身分證字號檢查邏輯 ...
        return true;
    }

    /**
     * Get the validation error message.
     *
     * @return string
     */
    public function message()
    {
        return '所提供的身分證字號格式不正確。';
    }
}

寫好之後,就可以在 Form Request 中像使用內建規則一樣使用它了:

use App\Rules\IsValidTaiwanId;

// ... in your Form Request
public function rules()
{
    return [
        'id_card_number' => ['required', new IsValidTaiwanId()],
    ];
}

看,是不是超優雅?複雜的邏輯被封裝起來,讓你的驗證規則像積木一樣可以隨意組合。

Middleware:請求的洋蔥式篩檢站

如果說 Validation 是檢查「證件內容」,那 Middleware 就是在門口檢查「入場券」的保全。它是一種過濾 HTTP 請求的機制。

Middleware 到底是什麼?把它想像成 API 的保全系統

你可以把 Middleware 想像成一層層的洋蔥。當一個請求進來時,它會先穿過最外層的 Middleware,如果通過,再進到下一層,直到最後抵達核心(你的 Controller)。在這個過程中,任何一層 Middleware 都可以決定要不要把這個請求擋下來,或者在請求上附加一些資訊再往下傳。

Laravel 內建了很多好用的 Middleware,例如 `auth` 用來檢查使用者是否登入,`throttle` 用來限制請求頻率(防止暴力破解)。而當內建的保全不符合你的需求時,就是我們自己動手打造的時候了。

實戰:打造一個檢查 API Key 的自訂 Middleware

假設我們有一個只開放給合作夥伴使用的 API,我們要求他們在請求的 Header 中帶上一個特定的 `X-API-KEY` 來驗證身份。

Step 1: 建立 Middleware

php artisan make:middleware VerifyApiKey

這會在 `app/Http/Middleware` 中建立 `VerifyApiKey.php`。我們來編輯它的 `handle` 方法:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class VerifyApiKey
{
    public function handle(Request $request, Closure $next)
    {
        $apiKey = $request->header('X-API-KEY');

        // 從 .env 取得我們預設的正確 API Key
        $validApiKey = config('services.partner.api_key');

        if (!$apiKey || $apiKey !== $validApiKey) {
            // 如果驗證失敗,直接回傳 403 Forbidden
            return response()->json(['message' => 'Unauthorized Access.'], 403);
        }

        // 驗證通過,將請求交給下一個 Middleware 或 Controller
        return $next($request);
    }
}

Step 2: 註冊 Middleware

光是建立還不夠,要讓 Laravel 認得它。打開 app/Http/Kernel.php,在 $routeMiddleware 陣列中加入一行:

protected $routeMiddleware = [
    // ... other middlewares
    'api.key' => \App\Http\Middleware\VerifyApiKey::class,
];

我們給了它一個簡短好記的名字 `api.key`。

Step 3: 套用到路由

現在,我們可以在路由檔案(例如 `routes/api.php`)中,把這個 Middleware 套用在需要保護的路由上:

use App\Http\Controllers\PartnerDataController;

Route::get('/partner-data', [PartnerDataController::class, 'index'])
     ->middleware('api.key');

搞定!現在任何要存取 `/partner-data` 這個端點的請求,都必須先通過 `VerifyApiKey` 這個保全的檢查,否則連 Controller 的門都摸不到。

組合技:Validation + Middleware,讓你的 Controller 瘦成一道閃電

現在我們把兩大神器組合起來。想像一下,我們的合作夥伴要透過 API 新增一筆訂單。這個請求需要:
1. 身份驗證(API Key 要對)。
2. 資料驗證(訂單欄位要符合格式)。

我們的路由會長這樣:

Route::post('/orders', [OrderController::class, 'store'])
     ->middleware('api.key');

然後我們的 `OrderController` 中的 `store` 方法,會使用前面提到的 Form Request 來處理驗證:

<?php

namespace App\Http\Controllers;

use App\Http\Requests\StoreOrderRequest; // 這是我們的 Form Request
use App\Models\Order;
use Illuminate\Http\Request;

class OrderController extends Controller
{
    public function store(StoreOrderRequest $request)
    {
        // 如果程式能執行到這裡,代表:
        // 1. API Key 已經通過 Middleware 驗證
        // 2. 請求的資料已經通過 StoreOrderRequest 的驗證

        // Controller 只需要專注在核心的商業邏輯
        $validatedData = $request->validated();

        $order = Order::create($validatedData);

        return response()->json($order, 201);
    }
}

看看這個 Controller,是不是乾淨到令人感動?它完全不用管驗證的細節,只專注於「建立訂單」這一件事。這就是關注點分離 (Separation of Concerns) 的最佳實踐!

工程師的小囉嗦:那些年我們踩過的坑

最後,分享幾個我在實務上總結出來的經驗:

  • Middleware 的順序很重要:在 `Kernel.php` 或路由群組中定義的 Middleware 是有執行順序的。例如,`auth` 應該放在 `throttle` 前面,避免未登入的訪客也佔用請求限制的額度。
  • 不要在 Middleware 中執行耗時操作:Middleware 應該要快狠準。如果你需要在每個請求都做複雜的資料庫查詢,考慮一下是不是有更好的架構,例如快取。
  • 自訂驗證規則的錯誤訊息要明確:不要只回傳「格式錯誤」,盡量告訴使用者「為什麼」錯,以及「該如何」修正。
  • 善用 `abort()` 輔助函數:在 Middleware 或 Form Request 的 `authorize` 方法中,使用 `abort(403, ‘You do not have permission.’)` 可以快速回傳標準的 HTTP 錯誤回應,程式碼更簡潔。
  • 記得寫測試!:你的自訂規則和 Middleware 也是程式碼的一部分,為它們撰寫單元測試或功能測試,確保它們在各種情境下都能如預期般運作。

掌握了 Laravel 的驗證與 Middleware,你不僅能寫出更安全、更穩固的程式碼,更能打造出結構清晰、易於維護的應用程式架構。這不只是技巧,更是一種工程師的品味與專業。希望今天的分享對你有幫助!

延伸閱讀

如果你們的團隊正在尋找專業的 Laravel 技術夥伴,或是對現有的系統架構感到頭痛,想進行一場徹底的健康檢查與優化,歡迎與浪花科技的團隊聊聊。我們樂於分享我們的經驗,協助你的專案走向成功。

常見問題 (FAQ)

Q1: Laravel 驗證 (Validation) 和中介層 (Middleware) 有什麼核心不同?

簡單來說,Validation 專注於「資料」的正確性,確保傳入的資料符合我們預設的格式、類型和業務規則。而 Middleware 專注於「請求」的合法性,它在請求到達 Controller 之前進行過濾,處理如身份驗證、權限檢查、請求頻率限制等橫切關注點 (Cross-Cutting Concerns)。Validation 是在檢查包裹裡的物品,Middleware 是在檢查送包裹的人有沒有通行證。

Q2: 什麼時候我應該建立自訂的驗證規則 (Custom Validation Rule)?

當 Laravel 內建的驗證規則無法滿足你的特定業務需求時,就應該建立自訂規則。例如:驗證台灣手機號碼格式、檢查統一編號是否有效、確認某個自訂的優惠碼是否存在且可用等等。將這些複雜邏輯封裝成一個獨立的 Rule Class,可以讓你的程式碼更乾淨、更易於重複使用和測試。

Q3: 什麼時候我應該建立自訂的 Middleware?

當你需要對「一組」路由或所有請求執行相同的「前置處理」或「後置處理」時,就適合使用自訂 Middleware。常見情境包括:檢查特定的 API 金鑰、根據使用者的角色或訂閱等級來限制存取、記錄所有傳入的 API 請求日誌、或是在系統維護時顯示一個維護中頁面。基本上,任何不屬於單一 Controller 核心業務邏輯,但又需要在請求生命週期中執行的檢查或操作,都很適合放在 Middleware 中。

Q4: 我可以對一個路由使用多個 Middleware 嗎?它們的執行順序是?

當然可以!你可以在路由定義中使用一個陣列來指定多個 Middleware,例如:->middleware(['auth:api', 'api.key', 'check.role:admin'])。它們的執行順序就是你在陣列中定義的順序,由左至右依序執行。請求會像穿越隧道一樣,依序通過每一個 Middleware 的檢查,只要其中一個 Middleware 決定阻擋請求,後續的 Middleware 和目標 Controller 都不會被執行到。

 
立即諮詢,索取免費1年網站保固