第五章:路由保護與全端資安:Middleware 設定與 Server Component 權限控管

前四章,我們完成了所有的地基建設。會員資料已經安全地躺在 Supabase 資料庫裡,我們也能隨時掌握他們的 Session 狀態。 現在,我們終於來到實作 SaaS 平台最核心的價值:「收費牆 (Paywall) 與防護網 (Route Protection)」。

如果你的系統沒有做好路由保護,使用者只要猜到網址(例如手動輸入 `https://vibe-tutor.com/courses/premium-video-1`),就能直接白嫖你的高價課程。 在 Next.js App Router 的架構下,我們擁有一套被業界稱為「雙重絕對防禦」的機制:Edge Middleware 攔截Server Component 資料庫鎖

本章節將帶你把這張防護網編織到滴水不漏。


🛡️ 第一層防禦:Edge Middleware (大門帶位員)

想像你的網站是一家會員制高級俱樂部。 Middleware 就像是站在俱樂部大門口的帶位員。當客人(HTTP Request)想走進「VIP 包廂區(`/dashboard` 或 `/courses`)」時,帶位員會瞬間掃描他胸前的名牌(Cookies)。 如果他沒有名牌(未登入),帶位員不會讓他進去浪費裡面的資源,而是會一腳把他踢到旁邊的「註冊櫃台(`/login`)」。

實作 Middleware

在 Next.js 中,Middleware 是一個非常特殊的檔案。它必須放在專案的根目錄(或 `src` 資料夾的根目錄),檔名必須完全叫做 `middleware.ts`。

建立 `src/middleware.ts`:

```typescript import { auth } from "@/auth" import { NextResponse } from "next/server"

// 由於 Middleware 運行在 Vercel Edge Runtime (邊緣運算節點), // 它極度輕量、極速,但也意味著它不能直接去連線複雜的 PostgreSQL 資料庫。 // auth() 函式在這裡會解密使用者的 Cookie 來判斷他是否登入。

export default auth((req) => { const isLoggedIn = !!req.auth;

// 取得使用者想要前往的完整網址路徑 const { pathname } = req.nextUrl;

// 定義陣列:哪些前綴的路由是絕對需要保護的? const protectedRoutes = ["/dashboard", "/courses/premium", "/settings"];

// 判斷當前路徑是否命中保護名單 const isProtectedPath = protectedRoutes.some(route => pathname.startsWith(route));

// 攔截邏輯:如果你想去保護區,但你卻沒有登入 if (isProtectedPath && !isLoggedIn) { // 我們不只要把他踢去登入頁,還要貼心地記下他原本想去哪裡 (callbackUrl) // 這樣他登入成功後,就會自動跳回他本來想看的影片頁面! const encodedCallback = encodeURIComponent(pathname); const redirectUrl = new URL(`/login?callbackUrl=${encodedCallback}`, req.url);

// 強制 307 跳轉
return NextResponse.redirect(redirectUrl);

}

// 額外邏輯:如果已經登入的人,還想跑去 /login 頁面,就直接把他趕回首頁 if (pathname === "/login" && isLoggedIn) { return NextResponse.redirect(new URL("/", req.url)); }

// 都沒問題,放行進入下一關 return NextResponse.next(); })

// Configuration (優化器):告訴 Next.js 不要攔截那些無聊的靜態檔案,以節省運算成本 export const config = { matcher: [ /* * Match all request paths except for the ones starting with: * - api (API routes) * - _next/static (static files) * - _next/image (image optimization files) * - favicon.ico (favicon file) * - 公開的圖片檔案 (例如 .png, .jpg) / '/((?!api|_next/static|_next/image|favicon.ico|.\\.png$).*)', ], } ```

💡 Vibe Coding 測試技巧: 寫完上面這段後,打開無痕視窗,直接在網址列輸入 `http://localhost:3000/dashboard`。 你會發現畫面幾乎沒有延遲,瞬間就跳到了 `/login` 頁面。這就是 Edge 邊緣運算的極致效能,保護你的伺服器免受未經授權的流量攻擊。


🏰 第二層防禦:Server Component 資料庫鎖 (內部保險箱)

Middleware 雖然快,但它有一個致命弱點:它只知道你「有沒有登入」,不知道你「有沒有付錢」。

假設駭客註冊了一個免費帳號,拿到了一般的會員 Cookie,他就能輕鬆騙過大門口的 Middleware 帶位員,大搖大擺地走進 `/courses/premium`。 這時候,我們需要在真正的渲染畫面(Server Component)裡,設立第二道絕對防禦。

打開你的付費課程頁面 `src/app/courses/premium/page.tsx`:

```typescript import { auth } from "@/auth" import { redirect } from "next/navigation" import { createClient } from "@/utils/supabase/server"

// 由於這是 Server Component,這段程式碼是在「伺服器端」執行的。 // 瀏覽器上的駭客絕對看不到這裡面的邏輯! export default async function PremiumCoursePage() { // 1. 取得使用者的 Session const session = await auth()

// 雖然 Middleware 已經擋過一次,但為了資安,後端必須再驗證一次 (防禦深度原則) if (!session?.user) { redirect("/login") }

// 2. 拿出使用者的 ID,去資料庫查詢他最私密的付費狀態 const supabase = createClient() const { data: userProfile, error } = await supabase .from("users") .select("membership_level, is_banned") .eq("id", session.user.id) .single()

// 3. 實施嚴格的業務邏輯攔截 if (error || !userProfile) { throw new Error("無法取得會員資料") }

if (userProfile.is_banned) { return

您的帳號已被停權,請聯絡客服。
}

if (userProfile.membership_level !== 'VIP' && userProfile.membership_level !== 'Pro') { // 客戶沒付錢!直接引導他去結帳頁面 return (

解鎖高級商業實戰教學

這是一堂價值 1999 元的高階課程,您目前的權限不足。

立即升級 VIP
) }

// 4. 全部驗證通過!終於可以把昂貴的商業機密渲染給他看了 return (

👑 歡迎來到頂級商業源碼殿堂

尊貴的 {session.user.name},您好!

  {/* 這裡放你的影片播放器或機密 Source Code 載點 */}
  <div className="aspect-video bg-black rounded-2xl flex items-center justify-center text-white/50 text-2xl shadow-2xl">
    這裡播放被嚴格保護的 4K 教學影片
  </div>
  
  <div className="mt-8 p-6 bg-yellow-50 border border-yellow-200 rounded-xl">
    <h3 className="font-bold text-yellow-800">機密原始碼下載:</h3>
    <a href="/api/download/source-code.zip" className="text-blue-600 underline">
      點擊下載 3999 方案專屬源碼
    </a>
  </div>
</div>

) } ```

💡 架構心法:為什麼要這麼麻煩寫兩次?

在早期的 SPA (Single Page Application, 如純 React/Vue) 架構中,常常會犯一個低級錯誤: 「在前端判斷是否為 VIP,如果是的話就 display: block,不是的話就 display: none」。 這種做法對於懂一點 Chrome 開發者工具的人來說,只需按 F12 把 CSS 改掉,或是去攔截 API 回傳的 JSON 改成 is_vip: true,就能瞬間破解你的收費牆。

但在 App Router 的 Server Component 架構中,如果使用者沒有通過我們在伺服器寫的 `if` 判斷,伺服器根本不會把影片連結和下載連結編譯進 HTML 裡面! 駭客就算把 F12 按爛,把瀏覽器炸掉,他也絕對拿不到根本沒有傳送過來的資料。這就是所謂的「後端絕對防禦」。


🎉 課程結業總結

太棒了!這五個章節是一段非常艱辛的旅程,但你成功撐過來了! 讓我們回顧一下你現在具備的駭客級實力:

  1. OAuth 2.0 原理精通:你了解了 Token 交換的底層邏輯,不再害怕面試官問你資安題。
  2. 多平台串接能力:你能從容應對 Google 與 Line 等各大科技巨頭複雜的開發者後台,並正確設定 Callback URLs。
  3. 資料庫架構思維:你成功把飄忽不定的 Cookie 升級成了穩若泰山的 Supabase Database Session 架構。
  4. 企業級資安防禦:你掌握了 Middleware 邊緣攔截與 Server Component 後端鎖的雙重防禦陣型。

你現在寫出來的登入系統,已經比坊間 80% 接案公司寫的還要安全、快速且現代化了。 請善用這些知識,去打造更多讓使用者安心掏錢的 SaaS 平台吧!我們在下一個進階專案見!

解鎖完整教學內容

本章為付費內容。加入專案即可解鎖超過 5000 字的深度解析,包含 10 個以上神級 Prompt 與真實 Source Code 範例!