LINE 登入串接中繼站 | 免申請 Channel 的架構設計
編譯摘要:LINE 登入串接中繼站(Cloudflare Workers)
第一步:濃縮
核心結論 1:當同一格設定問題每月重複出現、而根因在第三方平台的申請流程,該改的是架構不是文件。
- 圖文教學、影片、檢查清單三種形式都做過,每個月仍有人問同一格「Channel ID 要貼在哪裡」。
- 客戶要走的六步:註冊 LINE Developers → 建 Provider → 建 LINE Login Channel → 填 Callback URL → 複製 Channel ID/Secret → 若要推播再開一個 Messaging API Channel。
- 三個具體失敗模式:Callback URL 少一個斜線、Channel Secret 複製時多帶空白、Login 與 Messaging 是兩個 Channel 卻被當成同一個。
- 判準:問題出在第三方平台的申請流程而不是自己的程式,這段流程就該被包成一個按鈕。
核心結論 2:一對多中繼站的核心設計是信任邊界怎麼切,不是轉發本身。
- 持久資料只有兩張 D1 表:
sites(site_id、site_url、webhook_url、relay_secret_enc、site_key_enc、status)與account_links(line_user_id → site_id)。 - 金鑰儲存判準:只需要驗證就存 Hash,需要拿原文出來重簽就 AES-GCM 加密存。
relay_secret要對轉發出去的 Webhook 重新簽章,Hash 單向簽不回來,所以必須可逆加密;解密鑰匙RELAY_ENC_KEY只在 Workers 環境變數。 - Channel Secret 連加密存都不存,只放環境變數,D1 整個被撈走也換不到任何 token。
- KV 存三種短命資料:
nonce:600 秒、handoff:60 秒、resolve:1800 秒(後者是後來才加的熱路徑快取,解決高併發下 D1 查詢排隊)。
核心結論 3:OAuth callback 是無 nonce 的 top-level GET,四道防線缺一不可。
- state 自我驗證:
HMAC-SHA256(base64url(payload), BROKER_STATE_KEY),簽在 base64url 字串而非原始 JSON(避免欄位順序造成驗簽失敗),ts限 10 分鐘。用標準 HMAC 而非直接串接,可擋 length extension attack。 - nonce 一次性:寫入 KV、callback 消費即刪,補上純時間戳防護必然留下的時間窗口。
- token 絕不進網址:改用 60 秒
handoff_code,客戶後端走 server-to-server/oauth/redeem換回真 token,重複兌換直接回 410。 - 常數時間比對(
timingSafeEqual)防時序攻擊,逼攻擊者必須比完整串。 - Webhook 側:驗
X-Line-Signature必須用未經JSON.parse的原始 bytes,收件只做驗簽與入列後立刻回 200,背景以relay_secret重簽分流;批次合併 250 毫秒或 20 則先到先送,客戶端以事件 ID 去重。
第二步:質疑
前提假設:
- 營運者本身有一組審核通過的官方 Channel,且各方接受所有客戶的好友掛在這個帳號名下。 這是最強的前提。客戶只要要求品牌帳號必須是自己的,整套架構直接不適用,沒有折衷版本。
- 客戶的痛點是「設定卡關」而不是「資料主權」。 在意名單與帳號歸屬的客戶會認為中繼站是負債而非資產,這與 [[內容自主權 vs 託管便利]] 是同一條軸線上的相反端點。
- 流量形狀是「平常沒事、偶爾爆量」。 Workers 按請求計費在這個形狀下划算;若換成穩定高流量,一台固定規格的機器反而更便宜,選型理由會反轉。
- LINE 額度共用可接受。 推播額度算在 Channel 上,所有客戶共用同一組、尖峰互相排擠。這條在客戶數成長時會先於任何技術問題爆掉。
- 事件量在免費方案的容忍範圍內。 沒有 Queues,
ctx.waitUntil()背景轉發失敗沒有內建重試,靠客戶端事件 ID 去重來容錯。這是「至少一次」的補償設計,不是「恰好一次」的保證。 - NTP 時間同步正常。 state 的 10 分鐘有效期依賴時鐘,漂移過大時合法請求也會被判過期,與 [[重送攻擊防禦]] 條目已記錄的同一條局限。
換情境還成立嗎:
- 換第三方服務(金流、物流、CRM):授權收攏、token 集中保管、Webhook 重簽分流的骨架完全可移植,兩張資料表的形狀幾乎可以照抄。這是本文自己主張的遷移方向,成立。
- 換規模:客戶 3 家時中繼站是過度工程,直接教設定更快;客戶 300 家時額度共用與單點故障會先爆。中繼站的甜蜜區是「多到重複回答很痛、但還沒多到配額爆掉」,是一段區間而不是一個方向。
- 換平台服務條款:如果第三方平台禁止一組 Channel 服務多個獨立商家,這套架構的合規風險大於技術風險。文章完全沒展開這一層,卻是最致命的邊界條件。
- 換部署平台(AWS Lambda + DynamoDB / Vercel):Workers、D1、KV 的角色可以對應,但「KV 最終一致性」「D1 複雜 JOIN 有上限」這些具體邊界會換成另一組限制,設計判斷要重做。
反例與邊界條件:
- 中繼站把 N 個獨立故障合併成 1 個共同故障。 原本客戶各自的 Channel 掛掉只影響自己,現在中繼站掛掉全部客戶同時失效。可用性模型從「獨立失敗」變成「共同失敗」,這是集中化必然的帳。
- 沒有權限分級。 一組
site_key等於該站的全部能力,沒有 RBAC,與 [[三層 API 驗證]] 方法論記錄的同一條限制。 site_key只回傳一次降低的是「後台反覆曝光」的面積,擋不住管理員自己貼到公開頻道,與 [[API 金鑰管理]] 條目「Key 管理靠人」的局限一致,不能宣稱解決了它。- 「零 Channel 設定」是對客戶零,不是總成本歸零。 設定、合規與額度管理的責任被收到營運者身上,成本沒有消失,只是換人承擔。這一點文章沒有明說。
第三步:對標
跨域類比:
- 旅行社簽證代辦:旅客各自跑領事館備文件(自行申請 Channel)vs 交給旅行社統一送件(中繼站)。旅行社的價值不在跑得比較快,在於把一次性的學習成本攤提到上千件。代價也完全同構:你的簽證掛在旅行社的送件配額與信譽底下,旅行社出事你也走不了,對應「共用 LINE 額度」與「共同失敗」。
- 銀行聯行代收與清算中心:小銀行不各自跟央行清算,透過清算中心代收代付。清算中心持有等級最高的憑證、對每家成員銀行各發一組識別碼,跟中繼站持有 Channel Secret、對每站發
relay_secret是同一個形狀。金融業對這個模式的成熟答案是「清算中心必須有最高等級的可用性保證」,正好指出中繼站最該優先補的是 Durable Objects 那個坑,而不是再多加一道簽章。 - 電力變電所降壓:高壓(Channel Secret)集中在變電所,配到每戶的是低壓(
relay_secret)。用戶端不需要也不該接觸高壓,這解釋了為什麼「客戶拿不到」不是功能缺陷而是設計目標。降壓的代價同樣是變電所故障全區停電。 - 多租戶 SaaS 的租戶隔離:
sites表加每站專屬金鑰就是標準的 tenant isolation,一則 Webhook 進來要 route 到正確 tenant、resolve 快取、共用配額,這三題是 SaaS 多租戶的經典題目。差別只在這裡的「租戶」是客戶的整個網站而不是一個帳號。
知識庫定位:
本文在 wiki 中補上四個既有缺口,並產生一個新概念:
- [[Gateway 架構]] 從單租戶擴充到多租戶。 原條目只有 OpenClaw 一個來源,是「一個 Gateway 服務一個使用者」的形態,其「單點故障風險」寫的是「電腦關機服務中斷」。本文是第一個多租戶實例,把單點故障的嚴重性升級到「N 個客戶共同失敗」。
- [[重送攻擊防禦]] 的公開缺口被補上。 該條目局限性明寫「5 分鐘視窗內的重送仍可成功,若需更強防護可搭配 nonce」,本文的一次性 nonce(KV 600 秒、消費即刪)是這條建議的第一個實作證據。
- [[HMAC 驗證]] 的衝突標記獲得解法。 該條目標記舊文用的是「密鑰+欄位直接串接 SHA-256」而非標準 HMAC,有 length extension attack 風險。本文用標準 HMAC-SHA256,屬於同一團隊在高風險場景下的做法升級,衝突可標記為已解。
- [[API 金鑰管理]] 需要補判準,否則會誤導。 原條目主張「只存 Hash 不存明文」,本文用 AES-GCM 可逆加密,表面矛盾。實際上兩者適用條件不同:只需驗證存 Hash,需要拿原文出來重簽就必須可逆。不補這條判準,後續讀者會在需要重簽的場景錯用 Hash。
新產出概念 [[一對多信任邊界]]:既有的 [[Gateway 架構]] 與 [[三層 API 驗證]] 都是一對一信任模型,本文第一次處理「一個服務方持有最高權限憑證、N 個互不可見的租戶各持一份衍生憑證」的切法,以及它必然帶來的集中化代價。