beio Logobeio

【Cloudflare Workers 全端架構師之路 06】安全篇:守門員 Middleware、CORS 與 JWT 驗證

發布於

01. 前言:為什麼要在 Edge 做安全防護?

在傳統架構中,請求通常要穿過 Load Balancer、進入 Web Server、啟動 Application Runtime (如 Node.js),最後才在 Controller 層被 AuthMiddleware 擋下來。這浪費了大量的運算資源。

在 Cloudflare Workers 架構下,我們可以把這道防線推到最前線:

  • 驗證 Token: 如果 JWT 無效,直接在邊緣回傳 401,完全不觸發 D1 或 R2。
  • 阻擋攻擊: 如果 IP 在黑名單,直接斷開連線。

這就是 「將安全性左移 (Shift Security Left)」 到網路邊緣的概念。

02. Hono Middleware 機制

Hono 的核心是基於 洋蔥模型 (Onion Model) 的 Middleware 機制,這跟 Koa 或 Express 很像。

// Middleware 範例
app.use('*', async (c, next) => {
  const start = Date.now();
  await next(); // 進入下一個 Middleware 或主要邏輯
  const end = Date.now();
  c.header('X-Response-Time', `${end - start}ms`);
})

03. 實作:搞定 CORS (跨來源資源共用)

做前後端分離開發時,CORS (Cross-Origin Resource Sharing) 是最常見的惡夢。瀏覽器會先發送一個 OPTIONS 請求 (Preflight) 來詢問權限。

在 Workers 中處理 CORS 非常簡單且高效,因為 OPTIONS 請求可以在邊緣極速回應,幾乎不消耗成本。

import { Hono } from 'hono'
import { cors } from 'hono/cors'

const app = new Hono()

// 全域套用 CORS
app.use('/*', cors({
  origin: ['[https://your-frontend.com](https://your-frontend.com)', 'http://localhost:3000'], // 限制來源
  allowMethods: ['POST', 'GET', 'OPTIONS'],
  allowHeaders: ['Content-Type', 'Authorization'],
  exposeHeaders: ['Content-Length'],
  maxAge: 600, // 告訴瀏覽器:這個設定快取 10 分鐘,別一直問
  credentials: true,
}))

app.get('/', (c) => c.text('CORS is enabled!'))

04. 深度實作:JWT 身分驗證

我們不希望每次驗證使用者都要去查資料庫 (D1)。JWT (JSON Web Token) 是無狀態驗證的最佳解。我們將使用 hono/jwt 中介軟體。

步驟 1: 設定密鑰

# 生成一個隨機密鑰並存入 Cloudflare Secrets
openssl rand -hex 32
npx wrangler secret put JWT_SECRET

步驟 2: 實作登入 (簽發 Token) 與 驗證 (保護路由)

修改 src/index.ts

import { Hono } from 'hono'
import { jwt, sign } from 'hono/jwt'

type Bindings = {
  JWT_SECRET: string;
}

const app = new Hono<{ Bindings: Bindings }>()

// 1. 公開路由:登入並取得 Token
app.post('/auth/login', async (c) => {
  const { username, password } = await c.req.json();
  
  // (這裡省略真實的密碼驗證邏輯,請查 D1 比對 hash)
  if (username === 'admin' && password === 'password') {
    const payload = {
      sub: username,
      role: 'admin',
      exp: Math.floor(Date.now() / 1000) + 60 * 60, // 1小時過期
    }
    const token = await sign(payload, c.env.JWT_SECRET)
    return c.json({ token })
  }
  
  return c.json({ error: 'Unauthorized' }, 401)
})

// 2. 保護路由:使用 JWT Middleware
// 只有 /api/* 開頭的路徑需要驗證
app.use('/api/*', (c, next) => {
  const jwtMiddleware = jwt({
    secret: c.env.JWT_SECRET,
  })
  return jwtMiddleware(c, next)
})

// 3. 存取受保護資源
app.get('/api/profile', (c) => {
  // Hono 會自動把解碼後的 payload 放在 c.get('jwtPayload')
  const payload = c.get('jwtPayload')
  return c.json({
    message: `Hello, ${payload.sub}!`,
    yourRole: payload.role
  })
})

export default app

05. 進階防護:簡易 Rate Limiting (速率限制)

為了防止 API 被惡意刷爆,我們可以利用 Workers KV 實作一個簡單的計數器。雖然這不是精確的原子操作 (Atomic),但對於防護 DDoS 已經足夠有效。

(註:若需精確控制,建議使用 Cloudflare 官方的 Rate Limiting 產品或 Durable Objects)

import { Hono } from 'hono'

type Bindings = {
  KV: KVNamespace; // 記得在 wrangler.toml 綁定 KV
}

const app = new Hono<{ Bindings: Bindings }>()

// Rate Limiter Middleware
app.use('*', async (c, next) => {
  const ip = c.req.header('CF-Connecting-IP') || 'unknown';
  const key = `rate_limit:${ip}`;
  
  // 讀取當前次數
  const countStr = await c.env.KV.get(key);
  let count = countStr ? parseInt(countStr) : 0;
  
  if (count >= 100) { // 限制每分鐘 100 次
    return c.text('Too Many Requests', 429);
  }
  
  // 增加次數並設定 60 秒過期
  // 注意:這不是原子操作,在高併發下可能會少算,但作為防護已足夠
  await c.env.KV.put(key, (count + 1).toString(), { expirationTtl: 60 });
  
  await next();
})

06. 最佳實踐:環境隔離

在處理安全性時,切記不要把測試環境的 Token 用在正式環境。 利用 wrangler.tomlEnvironments 功能來隔離:

# wrangler.toml (Production)
[vars]
API_URL = "[https://api.myapp.com](https://api.myapp.com)"

# 開發環境 (Development)
[env.dev]
name = "my-api-dev"
[env.dev.vars]
API_URL = "http://localhost:8787"

執行時:

  • npm run deploy -> 部署到 Production
  • npm run deploy -- --env dev -> 部署到 Dev 環境

07. 小結與下一步

我們現在擁有了一個:

  1. 安全:有 JWT 保護與 CORS 設定。
  2. 抗壓:有基本的 Rate Limiting。
  3. 高效:所有驗證都在 Edge 完成,不浪費後端資料庫資源。

目前的架構雖然安全,但每個請求都是「獨立」的。如果我們需要做一個即時聊天室,或者需要保證庫存扣減的絕對順序性,目前的 Stateless 架構是做不到的。

我們需要一個「有狀態」且「強一致性」的地方。

在下一篇 Part 7: 狀態篇,我們將召喚 Cloudflare 的獨門黑科技 —— Durable Objects。這將是你從「Web 開發者」跨越到「分散式系統工程師」的關鍵門檻。


Ken Huang

關於作者

Ken Huang

熱衷於嘗試各類新技術的軟體開發者,現階段主力為 Android / iOS 雙平台開發,同時持續深耕前端與後端技術,以成為全端工程師與軟體架構師為目標。

最廣為人知的代表作為 BePTT。開發哲學是「以做身體健康為前提」,致力於在工作與生活平衡的基礎上,打造出擁有絕佳使用體驗的軟體服務。

這裡是用於紀錄與分享開發經驗的空間,希望能透過我的實戰筆記,幫助開發者解決疑難雜症並提升效率。

Android APP DevelopmentiOS APP DevelopmentBePTT CreatorFull Stack Learner