Session 管理是後台系統的核心安全基礎。
一個生產級的 Session 架構需要:
- 安全的 Cookie 設定(httpOnly、secure、sameSite)
- 高可用的 Session 儲存(Redis Sentinel)
- 多種登入方式的統一處理
- 使用者狀態的即時同步(Redis Cache)
- 活動式 TTL 重置(Rolling Session)
本文拆解一個真實 Nuxt 3 後台系統的完整 Session 架構。
整體架構圖
flowchart TB
subgraph Client["Client (Browser)"]
C1[Login Form]
C2[Cookie: nuxt-session]
end
subgraph Nuxt3["Nuxt 3 Server (Nitro)"]
N1[Redis Plugin\n初始化 Redis 連線]
N2[defineLogAndAuthHandler\nHOF: 每個請求驗證 Session]
N3[Login Handler\n寫入 Session]
N4[Redis Client Singleton\n跨登入類型共用]
end
subgraph Storage["Storage Layer"]
R1[(Redis Sentinel\nSession Store)]
R2[(Redis\nUser Status Cache)]
end
subgraph External["External Auth Services"]
E1[BackOffice SPI\nBasic Login]
E2[AD Server\nAD Login]
E3[IOWB Redis\nToken Login]
end
C1 -- POST /api/login --> N3
N3 --> E1
N3 --> E2
N3 --> E3
N3 -- set session --> R1
N3 -- del user cache --> R2
C2 -- sessionId --> N2
N2 -- read session --> R1
N1 -- inject redis --> N2
N4 -- connect --> R2
style N2 fill:#4f46e5,color:#fff
style R1 fill:#dc2626,color:#fff
style R2 fill:#dc2626,color:#fff
Session 安全設定(nuxt.config.ts)
1 | // nuxt.config.ts |
Cookie 安全設定說明
| 設定 | 值 | 防禦目標 |
|---|---|---|
cookieHttpOnly |
true |
防止 XSS 攻擊竊取 Session |
cookieSecure |
true |
防止中間人攻擊(強制 HTTPS) |
cookieSameSite |
'lax' |
防止 CSRF 跨站請求偽造 |
idLength |
64 |
增加 Session ID 猜測難度 |
rolling |
true |
活躍使用者不會被意外登出 |
Session 資料結構設計
每個登入成功的使用者,在 Redis 中儲存以下 Session 結構:
1 | interface SessionData { |
設計重點:
lastLoginTime不依賴 Cookie 的 TTL,而是在 HOF handler 中主動計算時間差,
搭配maxExpiryInSeconds環境變數,達到後端控制的 Session 過期機制。
Redis Plugin:連線注入(server/plugins/redis.ts)
1 | import { Redis } from 'ioredis'; |
設計重點:透過 Nitro Plugin 的
requesthook,每個 API handler 都可以直接
從event.context.redis取得 Redis 連線,無需自行建立連線。
Redis Singleton Pattern(server/utils/redis-client-singleton.ts)
某些特殊登入流程(如 IOWB Token Login)需要連到不同的 Redis 實例。
此時使用 Singleton Pattern 確保同一個連線字串只建立一個連線:
1 | import Redis from 'ioredis'; |
sequenceDiagram
participant A as IOWB Login Handler
participant S as RedisClientSingleton
participant R as IOWB Redis
A->>S: getInstance(iowbRedisUrl)
alt First call
S->>R: new Redis(connStr)
R-->>S: connection established
S-->>A: return new instance
else Subsequent calls
S-->>A: return existing instance
end
A->>R: get(token)
R-->>A: userId or null
三種登入流程
流程一:Basic Login(帳號密碼)
sequenceDiagram
participant U as User
participant N as Nuxt API
participant B as BackOffice SPI
participant R as Redis
U->>N: POST /api/login { userId, password }
N->>B: backOfficeSpiLoginUser(body)
B-->>N: { user: {...} }
N->>N: Check userStatus === 1
N->>R: DEL UserStatus:{userId} (清除狀態快取)
N->>N: Set session.isAuthenticated = true
N->>N: Set session.user = user
N->>N: Set session.lastLoginTime = now
N-->>U: { redirectTo: '/promotion/overview' }
關鍵程式碼:
1 | export default defineLogAndAuthHandler<LoginRequest>(async (event) => { |
流程二:AD Login(Active Directory)
sequenceDiagram
participant U as User
participant N as Nuxt API
participant AD as AD Server (via SPI)
participant R as Redis
U->>N: POST /api/login-with-ad { userName, txtKey }
N->>N: Extract Client IP (X-Forwarded-For header)
N->>AD: backOfficeSpiLoginAdUser { userName, txtKey, ip }
AD-->>N: { user: {...} }
N->>N: updateSession(event, { user, lastLoginTime, isAuthenticated: true })
Note over N: AD login 特殊處理:必須用 spread operator 觸發 session 儲存
N->>R: DEL UserStatus:{userId}
N-->>U: { redirectTo: '/promotion/overview' }
AD Login 特殊注意點:
1 | // ⚠️ @sidebase/nuxt-session 的內部機制需要完整替換 session 物件才能觸發儲存 |
流程三:IOWB Token Login(跨系統 SSO)
sequenceDiagram
participant IOWB as IOWB System
participant N as Nuxt API
participant IR as IOWB Redis
participant B as BackOffice SPI
IOWB->>N: GET /api/login-from-iowb?token=xxx&redirect_uri=yyy
N->>IR: GET token (verify token exists)
alt Token not found
IR-->>N: null
N-->>IOWB: 401 error_code=2
else Token valid
IR-->>N: userId
N->>B: backOfficeSpiLoginUser { userId, password }
B-->>N: { user: {...} }
N->>N: Set session
N-->>IOWB: { redirectTo: redirect_uri }
end
關鍵程式碼:
1 | export default defineLogAndAuthHandler(async (event) => { |
Session 過期:主動計算 vs Cookie TTL
本系統使用主動計算而非依賴 Cookie TTL:
1 | // 在 defineLogAndAuthHandler 中,每個請求都會檢查 |
| 機制 | 優點 | 缺點 |
|---|---|---|
| Cookie TTL(被動) | 自動,不需後端計算 | 無法強制登出特定使用者 |
| 主動計算(本系統) | 可精確控制、可即時強制登出 | 每次請求都需要計算 |
搭配 rolling: true,只要使用者持續操作,Session 不會過期。
若使用者超過 maxExpiryInSeconds 未操作,下次請求時後端主動踢除。
小結
| 功能 | 技術選擇 | 說明 |
|---|---|---|
| Session 儲存 | Redis Sentinel | 高可用、水平擴展 |
| Session 套件 | @sidebase/nuxt-session | Nuxt 3 官方推薦 |
| Redis 注入 | Nitro Plugin + request hook | 每個請求都有 redis context |
| 跨系統 Redis | Singleton Pattern | 避免重複建立連線 |
| Cookie 安全 | httpOnly + secure + sameSite | 防 XSS + CSRF |
| Session 過期 | 後端主動計算 + Rolling TTL | 精確控制 + 良好 UX |
| 多登入方式 | Basic / AD / IOWB Token | 統一 Session 結構 |