iT邦幫忙

2026 iThome 鐵人賽

DAY 19
0

day19_title

前言

在 Authorization 相關的系統中,近幾年來始終都是講求無狀態的方式處理授權相關的問題
最常見的做法就是 jwt,在早期 jwt 出現之前,大部分的系統要碼是以 cookies 配合 Session
的方式去處理授權相關問題,因為 jwt 是無狀態的,所以在授權有效之前還是會需要一個緩存處理

當然隨著系統的增大,jwt 作為微服務,基本上已經算是一個基本標配
但同時我們需要 Refresh Token 去處理相關問題,
Session 與 Jwt 並不是互斥的事情,相反混合架構可以使用兩者的優點去實現對系統的保障

簡易比較

我們為何需要做混合架構?以下我用一張圖表來展示相關議題

特性 純 JWT 純 Session JWT + Session 混合
無狀態驗證 ✅ 快 ❌ 需查 DB/Redis ✅ Access Token 免查詢
可即時撤銷 ❌ 難以撤銷 ✅ 刪除即失效 ✅ Refresh Token 可撤銷
跨服務驗證 ✅ 適合微服務 ❌ 需共享 Session Store ✅ Access Token 可跨服務
Token 外洩風險 高(長效期) 低(可即時清除) 低(Access Token 短效期)
實作複雜度 中高

大部分現在的工程師普遍以 jwt 作為短效期授權的解決方案,但要考慮 Refresh token 進行長效期的
解決方案可能較為生疏,今天我們就談及混合方案的部分

開始實作

架構圖

在開始實作前,我們需要了解一下整個藍圖(如下圖)要做什麼,以便我們去把整個部分做出來

認證流程架構圖

解釋

Access token (Jwt) : 短效期的 token 我們只用於存在記憶體或是 Authorization header 中
用來驗證 api 不查資料庫,

Refresh token : 長效型 token, 存在資料庫或是 redis 並且綁定 httpOnly cookies,用來換新的
Access Token,且可以被 server 主動撤銷(登出或是被盜用時強制下線)

這樣的模式是有專有名詞的,就是所謂的 Rotating Refresh Token 或是 Token Rotation

資料庫層 : Bun.sqlite

我們融合前面所有的知識點,bun 有內建的 sqlite 所以我們用 sqlite 去處理相關的,
我們建立兩張資料表 : users 還有 sessions 並且去關聯 user_id

users 與 sessions 資料表關聯

並且優化一些查詢的設定

可以參考以下的 code

import { Database } from 'bun:sqlite';

export const db = new Database('auth.sqlite', { create: true });

db.run(`
  PRAGMA journal_mode = WAL;

  CREATE TABLE IF NOT EXISTS users (
    id TEXT PRIMARY KEY,
    email TEXT UNIQUE NOT NULL,
    password_hash TEXT NOT NULL,
    created_at INTEGER NOT NULL
  );

  CREATE TABLE IF NOT EXISTS sessions (
    id TEXT PRIMARY KEY,
    user_id TEXT NOT NULL,
    user_agent TEXT,
    ip_address TEXT,
    expires_at INTEGER NOT NULL,
    revoked_at INTEGER,
    created_at INTEGER NOT NULL,
    FOREIGN KEY (user_id) REFERENCES users(id)
  );

  CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id);
`)

試跑看看結果

create_table_result

密碼處理 : Bun.password

bun 最威猛的就是他內部就有提供雜湊密碼的方法,以下提供 :

(這裡我們用 ./src/password.ts)

// 這個是雜湊用的
const hashPassword = async (input: string): Promise<string> => {
  return Bun.password.hash(input, {
    algorithm: 'argon2d',
    memoryCost: 19456, // 19 MB OWASP 建議值
    timeCost: 2,
  });
}

// 這個是驗證用的
const verifyPassword = async (input: string, hashInput: string): Promise<boolean> => {
  return Bun.password.verify(input, hashInput);
}

我們簡單驗證一下結果

hash_password_result

結論

目前已經完成的部分

  • 釐清了 Access Token 與 Refresh Token 的分工,以及 Token Rotation 與 Reuse Detection 的運作邏輯(登入、換發、登出、疑似遭竊時的處理流程)
  • 用 Bun.sqlite 建好 users 與 sessions 兩張表,並開啟 WAL 模式優化寫入效能
  • 用 Bun.password 完成密碼雜湊與驗證的基礎函式(採用 argon2d,並依 OWASP 建議設定 memoryCost)

下一天我們接續往 jwt 走!Go!Go!Go


上一篇
資料庫整合:Drizzle ORM + Bun + PostgreSQL 實戰
系列文
不只是快 —— Bun 30 天:從底層架構、全套工具鏈到生產部署19
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言