實戰範例 08:API 速率限制與安全防護
為 API 路由實作速率限制 (Rate Limiting) 防護,阻擋惡意暴力破解或 DDoS 攻擊。
/opsx:propose/opsx:apply/opsx:archive 實戰背景
開放的 API 介面如果缺乏防護機制,極易受到惡意爬蟲、暴力密碼破解 (Brute-force Attacks) 或阻斷服務攻擊 (DDoS)。為了保障系統的可用性與資訊安全,我們必須對高敏感 API 路由(例如:登入、註冊、發送驗證碼)實作速率限制 (Rate Limiting) 機制。
本範例中,我們將透過整合 Redis 與令牌桶演算法 (Token Bucket Algorithm),為 API 路由加上「單一 IP 每分鐘最多 60 次請求」的防護。我們將使用 OpenSpec 的 快速開發工作流 (Quick Flow) 來進行此項任務。
規格定義
本次變更計畫的 Capability 為 security-hardening。我們預計在規格增量 (Spec Delta) 中定義以下驗收標準:
## ADDED Requirements
### Requirement: API rate limiting
The system SHALL apply rate limits to all public endpoints to prevent abuse.
#### Scenario: Rate limit exceeded response
- **WHEN** an IP address exceeds 60 requests per minute to any API endpoint
- **THEN** the system SHALL reject subsequent requests with a 429 Too Many Requests status and return Retry-After header
端到端 6 輪 AI 協作紀錄
以下是 You 與 AI 助手的完整對話歷史。
── 第 1 輪:建立計畫與提案 ──
- You:
/opsx:propose "為全站 API 加上 Rate Limiting 限制,單一 IP 每分鐘限 60 次,超限回傳 429 與 Retry-After" - AI:
好的,已建立變更計畫
add-api-rate-limiting:- 建立 proposal.md: 說明限制機制、超限時的 429 狀態碼與 Retry-After 標頭。
- 建立 specs/security-hardening/spec.md: 新增超限響應的驗收場景 (Scenario)。
- 建立 tasks.md: 包含以下 4 個實作任務:
- 1.1 安裝並配置 Redis 連線服務
- 1.2 實作以 IP 為鍵值的速率計數器與 TTL 邏輯
- 1.3 建立 API 中間件 (Middleware) 進行超限阻擋
- 1.4 撰寫高頻請求測試案例驗證 429
請執行
/opsx:apply開始實作。
── 第 2 輪:審查與微調提案 ──
- You:
如果 Redis 斷線,不應該讓整站 API 掛掉,要有 Fail-Safe (安全降級) 機制,允許請求通過,並記錄 Error log。 - AI:
好的,已使用
/opsx:update更新文件:- 更新 design.md:設計 Fail-Safe 邏輯,當捕獲 Redis 連線錯誤時,記錄警告日誌 (Warn Log),並直接讓 API 請求通過 (Bypass)。
- 更新 tasks.md:在任務 1.3 中加入「Redis 斷線 Fail-safe 測試」的子任務。
修改已保持一致。現在您可以執行
/opsx:apply了。
── 第 3 輪:實作任務 1 (Redis 連線配置) ──
- You:
/opsx:apply - AI:
Implementing: add-api-rate-limiting (schema: spec-driven)
正在處理任務 1/4:安裝並配置 Redis 連線服務。
我已在專案中安裝
ioredis並於src/utils/redisClient.ts中配置完成:import Redis from 'ioredis'; export const redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379', { maxRetriesPerRequest: 1, // 快速失敗以配合 Fail-safe });✓ 任務已完成。已將 tasks.md 中的任務 1.1 標記為完成。
準備好進入任務 1.2「實作計數器邏輯」了嗎?
── 第 4 輪:實作任務 2 (速率計數器邏輯) ──
- You:
繼續實作計數器。 - AI:
Implementing: add-api-rate-limiting
正在處理任務 2/4:實作以 IP 為鍵值的速率計數器與 TTL 邏輯。
我已在
src/utils/rateLimiter.ts實作了利用 RedisMULTI指令進行原子性操作(增量與設定過期時間)的邏輯:import { redis } from './redisClient'; export async function isRateLimited(ip: string, limit = 60): Promise<{ limited: boolean; reset: number }> { const key = `ratelimit:${ip}`; try { const [count] = await redis.multi().incr(key).ttl(key).exec() as any; const currentCount = count[1]; if (currentCount === 1) { await redis.expire(key, 60); // 首次請求,設定 TTL 60秒 } return { limited: currentCount > limit, reset: 60 }; } catch (err) { console.error('Redis 速率限制防護異常:', err); return { limited: false, reset: 0 }; // Fail-safe 降級直接放行 } }✓ 任務已完成。已將任務 1.2 標記為完成。
下一步將實作 API 路由中間件。是否繼續?
── 第 5 輪:實作任務 3 (中間件超限阻擋) ──
- You:
繼續實作中間件。 - AI:
Implementing: add-api-rate-limiting
正在處理任務 3/4:建立 API 中間件 (Middleware) 進行超限阻擋。
我已在
src/middleware.ts中攔截 API 請求並套用防護邏輯:import { isRateLimited } from './utils/rateLimiter'; export async function onRequest({ request, next }) { const url = new URL(request.url); if (url.pathname.startsWith('/api/')) { const ip = request.headers.get('x-forwarded-for') || '127.0.0.1'; const { limited, reset } = await isRateLimited(ip); if (limited) { return new Response(JSON.stringify({ error: '請求過於頻繁' }), { status: 429, headers: { 'Content-Type': 'application/json', 'Retry-After': String(reset), }, }); } } return next(); }✓ 任務已完成。已將任務 1.3 標記為完成。
剩下最後一個測試任務。是否繼續?
── 第 6 輪:撰寫測試、驗證與歸檔 ──
- You:
執行測試,verify 並歸檔。 - AI:
Implementing: add-api-rate-limiting
正在處理任務 4/4:撰寫高頻請求測試案例。
我已在
tests/rateLimit.test.ts中實作測試。- 連續發送 61 次請求,第 61 次成功被攔截並返回 429 狀態碼。
- 測試了當 Redis 被斷開連線時,Fail-safe 邏輯能直接放行請求。
✓ 4/4 任務全數完成。
自動驗證與歸檔:
- 驗證成功:實作完全符合
Scenario: Rate limit exceeded response的要求。 - 智慧同步:已將暫存規格合併至主規格 openspec/specs/security-hardening/spec.md。
- 目錄歸檔:已將計畫資料夾移至
openspec/changes/archive/2026-07-14-add-api-rate-limiting/。
Archive Complete
變更計畫已成功歸檔!