標籤: REST API

  • 企業級多租戶認證:Token 驗證實戰

    商業價值:多租戶認證讓「一套系統服務多個商戶」,是 SaaS 模式的技術基礎。這直接支撐 導讀篇提到的 70% 人力成本降低(多商戶共用系統,不用每家都建一套)。

    前言:SaaS 系統的認證挑戰

    在多租戶 SaaS 系統中,我們面臨兩種使用者:

    角色 說明 存取範圍
    商戶 (Merchant) 使用系統的客戶 只能看到自己的資料
    平台 (Platform) 系統管理者 可以看到所有商戶資料
    安全需求:

    • 商戶 A 絕對不能看到商戶 B 的訂單
    • Token 被盜用時要能快速失效
    • 支援多裝置同時登入

    Token 設計

    Token 結構

    {
    “tokenId”: “uuid-xxxx-xxxx”,
    “userType”: “MERCHANT”,
    “userId”: “U001”,
    “merchantId”: “M001”,
    “permissions”: [“ORDER_READ”, “ORDER_WRITE”],
    “issuedAt”: “2024-03-18T10:00:00Z”,
    “expiresAt”: “2024-03-18T12:00:00Z”
    }

    Token 驗證流程

    請求進來


    ┌─────────────────────┐
    │ 1. 檢查 Token 存在 │
    └──────────┬──────────┘


    ┌─────────────────────┐
    │ 2. 檢查 Token 有效 │ ──── 過期/無效 ──→ 401 Unauthorized
    └──────────┬──────────┘


    ┌─────────────────────┐
    │ 3. 載入使用者資訊 │
    └──────────┬──────────┘


    ┌─────────────────────┐
    │ 4. 設定安全上下文 │
    └──────────┬──────────┘


    繼續處理請求

    Filter 實作

    @Component
    public class TokenAuthFilter extends OncePerRequestFilter {

    @Autowired
    private TokenService tokenService;

    @Autowired
    private TokenCache tokenCache;

    @Override
    protected void doFilterInternal(
    HttpServletRequest request,
    HttpServletResponse response,
    FilterChain chain) throws Exception {

    // 1. 從 Header 取得 Token
    String token = extractToken(request);

    if (token == null) {
    sendError(response, “Missing token”);
    return;
    }

    // 2. 驗證 Token(先查快取)
    TokenInfo tokenInfo = tokenCache.get(token);
    if (tokenInfo == null) {
    tokenInfo = tokenService.validate(token);
    if (tokenInfo != null) {
    tokenCache.put(token, tokenInfo);
    }
    }

    if (tokenInfo == null || tokenInfo.isExpired()) {
    sendError(response, “Invalid or expired token”);
    return;
    }

    // 3. 設定安全上下文
    SecurityContext.set(tokenInfo);

    try {
    chain.doFilter(request, response);
    } finally {
    SecurityContext.clear();
    }
    }

    private String extractToken(HttpServletRequest request) {
    String header = request.getHeader(“Authorization”);
    if (header != null && header.startsWith(“Bearer “)) {
    return header.substring(7);
    }
    return null;
    }
    }


    商戶隔離

    所有資料存取都要加上商戶過濾:

    @Repository
    public class OrderRepository {

    public List<Order> findOrders(OrderQuery query) {
    // 從安全上下文取得當前商戶
    TokenInfo token = SecurityContext.get();

    if (token.isMerchant()) {
    // 商戶只能查自己的資料
    query.setMerchantId(token.getMerchantId());
    }
    // 平台角色可以查所有商戶(不加過濾條件)

    return jdbcTemplate.query(
    “SELECT * FROM orders WHERE merchant_id = ? AND …”,
    query.getMerchantId(),

    );
    }
    }

    安全保證:即使前端傳入其他商戶 ID,後端也會強制覆蓋為當前登入商戶的 ID。

    Token 快取策略

    策略 設定 原因
    快取時間 5 分鐘 減少 DB 查詢,但不會太舊
    最大數量 10,000 避免記憶體爆炸
    淘汰策略 LRU 不常用的 Token 先移除
    @Bean
    public Cache<String, TokenInfo> tokenCache() {
    return Caffeine.newBuilder()
    .maximumSize(10_000)
    .expireAfterWrite(Duration.ofMinutes(5))
    .recordStats()
    .build();
    }

    Token 失效機制

    當需要強制登出時:

    @Service
    public class TokenService {

    /**
    * 強制失效單一 Token
    */

    public void revokeToken(String tokenId) {
    // 1. 從資料庫標記失效
    tokenRepository.markRevoked(tokenId);

    // 2. 從快取移除
    tokenCache.invalidate(tokenId);

    // 3. 發布失效事件(通知其他節點)
    eventPublisher.publish(new TokenRevokedEvent(tokenId));
    }

    /**
    * 強制登出商戶所有 Token
    */

    public void revokeAllTokens(String merchantId) {
    List<String> tokens = tokenRepository.findByMerchant(merchantId);
    tokens.forEach(this::revokeToken);
    }
    }


    權限檢查

    @RestController
    public class OrderController {

    // 需要 ORDER_READ 權限
    @RequirePermission(“ORDER_READ”)
    @GetMapping(“/api/orders”)
    public List<Order> listOrders() {
    return orderService.findOrders();
    }

    // 需要 ORDER_WRITE 權限
    @RequirePermission(“ORDER_WRITE”)
    @PostMapping(“/api/orders/{id}/ship”)
    public Order shipOrder(@PathVariable String id) {
    return orderService.ship(id);
    }
    }


    安全總結

    安全機制 實作方式 防護目標
    Token 驗證 Filter 攔截 未授權存取
    商戶隔離 強制覆蓋 merchantId 資料外洩
    Token 快取 Caffeine + 分散式同步 效能
    強制失效 事件發布 + 快取清除 帳號被盜
    權限檢查 註解 + AOP 越權操作

    為什麼不用其他方案?

    方案 優點 缺點 結論
    Session-based 簡單、狀態伺服器端管理 不適合分散式、需要 Session 複製 單機可用
    JWT(無狀態) 不需查 DB、自包含 無法主動失效、Token 可能很大 短期 Token 可用
    OAuth 2.0 標準協議、支援第三方 複雜、需要額外服務 需要 SSO 時用
    Token + 快取 可主動失效、效能好 需要 Redis SaaS 系統首選

    實戰踩坑

    坑 1:商戶資料外洩

    早期版本有個 API 忘記加商戶過濾,某商戶發現可以透過改 URL 的 orderId 看到別人的訂單。好險被內部測試發現,立刻修復並全面檢查所有 API。教訓:所有 Repository 方法預設都要加 merchantId 過濾

    坑 2:Token 快取同步問題

    三台 Server,使用者在 A 機登出,但 B、C 機的快取還有 Token。結果登出後還能繼續操作 30 秒。解法:登出時發布事件到所有節點,同步清除快取。

    坑 3:權限設計太細

    最初設計了 50+ 種權限,結果商戶搞不懂、客服也解釋不清。後來簡化成 5 個角色(管理員、主管、操作員、財務、唯讀),大幅降低維護成本。


    系列導航

    ◀ 上一篇
    Kafka 事件驅動
    📚 返回目錄 下一篇 ▶
    健康檢查
  • WordPress REST API 調試實戰:從 NNNN 字符到完整修復

     

    WordPress REST API 調試實戰:從 NNNN 字符到完整修復

     

    🎯 重點摘要

    • 問題根源:WordPress 資料庫中存在字面 ‘n’ 字符(0x6E),而非換行符(0x0A)
    • 表現症狀:REST API 返回 NNNN、n< 模式、表格損壞、內容亂碼
    • 根本原因:多層架構的信息轉換導致錯誤的故障假設和調試方向偏離
    • 解決方案:使用 od -c 檢查二進位數據、多層驗證、直接在資料庫層修復

    問題是如何出現的?

    WordPress REST API 調試中最常見的陷阱就是 症狀與原因的巨大落差。你在前端看到 NNNN 字符和 n< 模式,但實際問題可能在完全不同的地方。

    這篇文章根據真實的 WordPress 修復案例(超過 1,000 個 NNNN 字符、16 個表格損壞),詳細解析多層故障排除流程。

    第 1 層:表面症狀 vs 實際原因

    當 REST API 返回異常內容時,最危險的假設就是直接指責過濾器或編碼問題。實際上,以下三層都可能是問題來源:

    你看到的 期望的原因 實際原因 解決難度
    REST API 顯示 n< 過濾器損壞內容 資料庫中有字面 ‘n’ 字符 ⭐⭐⭐⭐
    NNNN 字符出現 轉義或編碼問題 ‘nn’ 模式(字面n + 換行) ⭐⭐⭐⭐⭐
    表格消失或亂碼 HTML 結構破壞 字面 ‘n’ 阻斷了 HTML 標籤解析 ⭐⭐⭐⭐

    表 1:WordPress 調試常見誤判清單 — 本表格列出 REST API 常見症狀、直觀的誤判原因,以及實際根本原因。這些誤判會導致調試花費 2-4 小時無果。

    第 2 層:多層架構的信息失真

    WordPress 資料從資料庫到瀏覽器經過多個轉換層,每一層都會改變你看到的表現形式:

    層級 你看到的 實際的字節 驗證方式
    MySQL 命令列 n(轉義序列) 0x0A(真實)或 0x6E(’n’ 字符) od -c
    PHP 讀取 實際換行或字面 ‘n’ 二進位正確表示 strpos($str, “n”)
    REST API JSON n 字符或 n JSON 正確轉義 jq + od -c
    瀏覽器顯示 NNNN、亂碼或正常 HTML 渲染結果 DevTools 檢查

    表 2:多層架構信息轉換對比 — 同一份資料在不同層級呈現出不同的表現。MySQL 命令列使用轉義表示,PHP 使用二進位,REST API 使用 JSON,瀏覽器進行 HTML 渲染。如果不理解這些轉換,很容易做出錯誤的根因判斷。

    第 3 層:正確的調試順序

    大多數 WordPress 調試問題都是因為調試順序錯誤。正確的調試順序應該是:

    1. 直接檢查二進位資料(od -c)— 這是源頭事實,必須第一步做
    2. 對比 DB ↔ Filter ↔ REST API 的三層輸出 — 縮小問題範圍
    3. 假設反轉 — 如果不是編碼問題,那是資料損壞嗎?
    4. 定位損壞位置 — 哪一層引入的?是資料庫本身還是更新時損壞?
    5. 追蹤操作歷史 — 之前做過什麼導致損壞?

    在真實案例中,調試花費了大量時間的原因是:第 1 次調查順序是 2 → 3 → 1 → 4 → 5,而正確順序應該是 1 → 2 → 3 → 4 → 5。

    第 4 層:實際的修復步驟

    步驟 1:使用 od -c 檢查資料庫的實際字節

    docker exec wordpress mysql -u wpuser -pwp_password wordpress -e 
      "SELECT SUBSTRING(post_content, POSITION('' IN post_content), 50) 
       FROM wp_posts WHERE ID = 984;" | tail -1 | od -c | head -20

    輸出應該顯示:

    !   -   -   >   n      n   <   !   -   -
                        ^   ^
                字面'n'  實際換行

    如果看到這個模式,你已經找到了根本原因:資料庫中有字面 ‘n’ 字符

    步驟 2:修復資料庫損壞

    docker exec wordpress mysql -u wpuser -pwp_password wordpress -e "
    UPDATE wp_posts
    SET post_content = REPLACE(post_content, CONCAT('n', CHAR(10)), CHAR(10))
    WHERE ID = 984;
    "

    這個 SQL 語句移除所有「字面 ‘n’ + 換行符」的組合,只保留實際的換行符。

    步驟 3:驗證修復

    curl -s http://localhost:8001/wp-json/wp/v2/posts/984 | jq -r '.content.rendered' | grep -o 'n<' | wc -l
    # 應該返回 0

    第 5 層:為什麼調試這麼困難?

    困難點 為什麼 解決方案
    信息不對稱 MySQL 顯示 n、PHP 顯示實際換行、REST API 顯示 n 字符 建立單一源頭(od -c),在那層定位問題
    問題來源不清 用戶說「做表格後出現 NNNN」,但不知道之前對資料做過什麼 追蹤操作歷史,理解損壞何時引入
    多層架構複雜 Database → Filter(6 個) → REST API → Browser 逐層檢查,縮小問題範圍到特定層級
    工具轉換多次 MySQL CLI → od -c → PHP → curl → jq → JSON 固定驗證工具,避免多次轉換導致的失真

    表 3:WordPress REST API 調試困難點分析 — 列出調試過程中的四個主要困難,以及每個困難對應的解決方案。這些都是基於真實的修復案例總結出來的。

    第 6 層:最佳實踐清單

    • 第一步永遠是 od -c — 不要猜測,直接看二進位數據
    • 建立多層驗證 — 不要只檢查一層,Database + Filter + REST API 都要查
    • 假設反轉 — 一個方向卡住了,立即反轉假設方向
    • 追蹤操作歷史 — 理解「之前發生了什麼」比「現在看起來怎樣」更重要
    • 表格要有邊框 — 使用 inline style: style="border: 1px solid #333; padding: 8px;"
    • 保存配置檔 — WordPress API 認證信息應該存在 ~/.claude/projects/project-name/wordpress-config.env

    常見問題(FAQ)

    總結

    WordPress REST API 調試的關鍵是理解 多層架構中的信息失真。症狀永遠不等於原因,你看到的 NNNN 字符只是冰山一角。

    記住這個優先順序:

    1. od -c 檢查二進位(源頭事實)
    2. 逐層驗證(Database → Filter → REST API)
    3. 假設反轉(卡住時反向思考)
    4. 追蹤歷史(理解根本原因)
    5. 修復並驗證(修完要驗證三層)

    下次遇到 WordPress REST API 問題時,不要急著改過濾器或重建資料庫。先用 od -c 看看真正的二進位數據,一切就清楚了。

     

  • 爬蟲機(OnGCP)

    總結

    需要做的事情
    1. GCP帳號
    2. 開VM
    3. 多台VM 指定一台為母機
    4. 其餘為子機
    5. 安裝DOCKER 並在子母機上設定關聯

    (閱讀全文…)

  • 各家API的比較

    目標們:
    * 91APP
    * cyberbiz
    * EasyStore
    * friday
    * iopenmall
    * MOMO
    * MO+
    * PChome
    * shopify
    * Shopline
    * Yahoo購物中心
    * 樂天
    * 東森
    * 博客來
    * 蝦皮
    * Coupang
    * 露天

    (閱讀全文…)