iT邦幫忙

2026 iThome 鐵人賽

DAY 24
0
Software Development

一套真實運作中的 Laravel 系統,拆解它的原生機制系列 第 24 篇

Day 24:Middleware 實戰——CSP header 跟業務導流邏輯,兩種輕量 middleware 長什麼樣

  • 分享至 

  • xImage
  •  

前言:Middleware 不是只能做「技術性攔截」

「Middleware 就是驗證登入、檢查權限那種東西吧?」

這是很多人對 middleware 最直覺的印象——確實,驗證登入、檢查權限是 middleware 最常見的用途,但 middleware 能做的事情比這廣得多:只要是「在請求進到 controller 之前、或回應送出使用者之前,需要統一處理的邏輯」,都可以放進 middleware。今天用兩支結構完全不同的真實 middleware,看看這個機制的彈性有多大。

今日目標

  • 看一支最小可行的 middleware:只做一件事,加一個安全性標頭
  • 看另一支 middleware:把「依網域判斷該不該重導」這種業務邏輯放進路由層
  • 理解 middleware 適合放什麼樣的邏輯,又不適合放什麼樣的邏輯
  • 認識這個系列後面會深入的一支更複雜的 middleware(先賣個關子)

本文主體

最小可行 middleware:加一個 header

先看第一支:一支只做一件事的 middleware,職責是替每個回應加上 Content-Security-Policy(CSP)這個安全性標頭,內容從設定檔讀取。

這支 middleware 的邏輯精簡到幾乎沒有分支:呼叫 $next($request) 拿到 controller 產生的回應,替它加一個標頭,回傳。沒有判斷條件、沒有提前中斷請求,是 middleware 能做到的最小規模。

這種「加一個回應標頭」的需求,天生就適合放進 middleware,而不是散落在每個 controller 裡各自加——理由很直接:CSP 這種安全性標頭應該是「全站一致」的規則,如果讓每個 controller 自己決定要不要加,遲早會有某個 controller 忘記加,變成防護有漏洞的那一個。集中放進 middleware,等於用架構上的方式保證「這條規則不會被漏掉」。

第二支:把業務邏輯放進路由層

再看第二支,複雜度明顯高一階:它的職責是判斷「這個請求存取的網域,跟這個路由『應該』對應的網域是不是一致」,不一致就重導到正確的網域。

具體情境是這樣:這個系列的素材專案有多個對外網域,同一個路由名稱(例如某支新聞的顯示頁)理論上要用某個特定網域存取。這支 middleware 攔在路由分派之後、controller 執行之前,檢查目前的網域參數跟路由「應該」對應的網域是否一致——不一致就重導,讓使用者最終落在正確的網域上。

這支 middleware 值得注意的地方是:它讀取了路由參數、判斷了資料型別(是不是新聞頁面、頁面本身的 type 是不是新聞類型),這已經不是單純的技術性攔截,而是業務邏輯——「什麼情況下該重導去新聞網域」本身就是一條業務規則,只是這條規則被放在 middleware 這個位置執行,而不是寫進 controller。

兩種放法的取捨:什麼情況該放進 middleware

看完這兩個例子,可以整理出一個判斷依據:一段邏輯適不適合放進 middleware,關鍵不在於它是「技術性」還是「業務性」,而在於這段邏輯是不是要在多個路由/多個 controller 之間一致套用、而且發生的時機是在「進入 controller 之前」或「回應送出之前」。

加 CSP 標頭符合這個條件——全站每個回應都要加。網域重導邏輯也符合——只要是牽涉到特定路由的請求,進 controller 之前就要先判斷完,controller 本身不需要知道這件事發生過。

反過來說,如果一段業務邏輯只跟某一個特定 controller 的行為有關,跟其他路由完全無關,那就不該硬塞進 middleware——middleware 是全域或群組層級的機制,把只跟單一情境相關的邏輯放進去,會讓這支 middleware 變得難以理解「它到底管什麼」。

用一組對照來看:

✅ 適合放進 middleware
- 全站一致的安全性標頭(CSP)
- 多個路由都要套用的存取控制/重導規則
- 進 controller 前就能判斷完、不需要 controller 知道的邏輯

❌ 不適合硬塞進 middleware
- 只跟單一 controller/單一 action 相關的邏輯
  → 這種邏輯留在 controller 裡,或抽成 controller 自己呼叫的方法,
    塞進 middleware 只會讓「這支 middleware 在管什麼」變得難以理解

先賣個關子:還有一支更複雜的 middleware

這個系列的素材專案裡,還有一支 middleware 承擔了比前面兩支都複雜得多的職責——它會依照請求的 session 或參數,動態決定整個網站要用哪一套畫面呈現,牽涉到 Laravel 的 View 尋找機制。這支 middleware 值得獨立一整篇來講,本篇先不展開——明天先講另一個主題,這支 middleware 的完整機制留到後面幾天深入。

今日思考題

回想你的專案裡,有沒有一段邏輯目前散落在多個 controller 裡各自重複實作,但其實符合「全站一致、進 controller 前就能判斷完」這個條件,適合收斂成一支 middleware?

今日重點回顧

  • Middleware 不是只能做「驗證登入」這種技術性攔截,只要符合「多個路由要一致套用、發生在進 controller 前後」的邏輯都適合放進去
  • 最小可行 middleware 範例:全站統一加一個安全性標頭
  • 進階範例:依網域判斷是否重導的業務邏輯,放進路由層而不是 controller 裡各自判斷
  • 判斷一段邏輯該不該放進 middleware,關鍵在於「一致性」跟「時機」,不在於它是技術性還是業務性

明日預告

明天換一個主題,看檔案儲存的路徑設計——一個只有十幾行的類別,怎麼讓上傳的檔案路徑從一串難以排查的雜湊值,變成人一眼就能看懂的結構。


上一篇
Day 23:Trusted Proxies——Docker 環境下抓不到真實 IP 的坑
系列文
一套真實運作中的 Laravel 系統,拆解它的原生機制 共 24 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言