---
title: SR-01 身分驗證
eyebrow: Guidant AI 資安檢視總報告 · 安全需求（SR）
h1: SR-01 身分驗證
subtitle: 人、機器、外部身分提供者
lede: 「你是誰」要真的驗，不收自報。這一章 **8 條**：人的登入與忘記密碼、OAuth 回呼、機器（代理程式）的報到與通行證、即時連線的身分。6 條有掃描實例、2 條是標準做法。
chips:
  - { text: "8 條", kind: accent }
  - { text: "掃描驗證 6", kind: ok }
  - { text: "標準做法 2", kind: plain }
---

> 讀法見[總表](SR-00-overview.html)。每條六欄：要求／為什麼／適用連線／對應威脅／驗證方式／來源。現況不在這裡，看[符合性矩陣](SR-matrix.html)。

## 這一章管什麼

系統有三種「誰」要驗：**人**（瀏覽器那頭的使用者，走密碼、LDAP、雙因子）、**機器**（客戶機房的代理程式，走憑證與簽章通行證）、**外部身分提供者**（Google、客戶的 AD／LDAP——我們不管密碼，但要確認提供者真的說了「是他」）。三種的共同原則只有一條：**身分由我們簽發或向提供者驗證，不收請求裡自報的**。掃描抓到的 6 條實例全是違反這一條的某種形狀——忘記密碼把鑰匙直接回給請求者、LDAP 只查人不驗密碼、Google 登入是空殼、代理程式自報編號就信。

| # | 要求 | 來源 |
|---|---|---|
| SR-01.1 | 不需登入的入口，回應不得帶可直接使用的祕密 | 掃描驗證 |
| SR-01.2 | 外部身分提供者登入或綁定，必須真的向提供者驗證 | 掃描驗證 |
| SR-01.3 | 登入失敗鎖定涵蓋所有登入路徑 | 標準做法 |
| SR-01.4 | OAuth 回呼同時驗連結與瀏覽器 | 掃描驗證 |
| SR-01.5 | 安全計數不得只存易失儲存 | 標準做法 |
| SR-01.6 | 機器身分只認簽章通行證，每請求查撤銷 | 掃描驗證 |
| SR-01.7 | 機器報到比其他端點更嚴 | 掃描驗證 |
| SR-01.8 | 即時連線每個事件重注入伺服器端身分 | 掃描驗證 |

---

## SR-01.1 不需登入的入口，回應不得帶可直接使用的祕密 {#sr-01-1}

| 欄位 | 內容 |
|---|---|
| **要求** | 不需登入就能打的端點（忘記密碼、驗證碼、邀請），回應**不得**帶任何可直接使用的祕密——重設鑰匙、權杖、一次性碼。祕密只能走第二通道（信箱、簡訊）送到本人。成功與失敗的回應必須逐位元組相同，不讓人靠回應差異探信箱存不存在 |
| **為什麼** | 鑰匙從回程直接交給按送出的人，等於任何知道信箱的人都能接管那個帳號——包含原廠最高權限管理員 |
| **適用連線** | [C01](../connections/C01-browser-to-frontdoor.html) 瀏覽器 → 前門（不需登入的入口都在這條線上暴露） |
| **對應威脅** | C01 [T1 不需登入的入口把祕密從回程交出去](../connections/C01-browser-to-frontdoor.html) 🔴 |
| **驗證方式** | 不登入，對忘記密碼端點送一個存在的信箱與一個不存在的信箱，兩次回應的狀態碼與內容逐位元組相同，且內容不含任何 uid／token／code 欄位 |
| **來源** | 掃描驗證：C01 D1（實例 [M13-1](../STRIDE/S-spoofing.html#m13-1)） |

## SR-01.2 外部身分提供者登入或綁定，必須真的向提供者驗證 {#sr-01-2}

| 欄位 | 內容 |
|---|---|
| **要求** | 以外部身分提供者（Google、客戶 AD／LDAP）登入或綁定帳號時，**必須**真的向提供者驗證：OAuth 要驗簽、驗受眾、驗效期；LDAP 要用「查到的 DN ＋ 使用者輸入的密碼」實際 bind 一次。**不得**收呼叫者自填的外部帳號編號當作已驗證 |
| **為什麼** | 「用 Google 身分」的入口若不向 Google 驗證，攻擊者填目標的 Google 編號就綁進自己帳號；LDAP 只查人不驗密碼，等於任何知道帳號的人都能登入 |
| **適用連線** | [C04](../connections/C04-browser-to-api-drive-oauth.html) 瀏覽器 → api（OAuth 回呼）、[C11](../connections/C11-api-to-ldap.html) api → 客戶 LDAP／AD |
| **對應威脅** | C04 [T3 「用 Google 身分」的入口沒真的向 Google 驗證](../connections/C04-browser-to-api-drive-oauth.html) 🟠、C11 [T1 只查人、不驗密碼就放行](../connections/C11-api-to-ldap.html) 🟠 |
| **驗證方式** | LDAP：以正確帳號＋錯誤密碼登入，必須 401 且目錄伺服器日誌看得到一次失敗的 bind。OAuth：對綁定端點送一個自填的 Google 帳號編號、不帶 id_token，必須被拒 |
| **來源** | 掃描驗證：C04 D3（實例 [M13-5](../STRIDE/S-spoofing.html#m13-5)）、C11 D1（實例 [M13-2](../STRIDE/S-spoofing.html#m13-2)） |

## SR-01.3 登入失敗鎖定涵蓋所有登入路徑 {#sr-01-3}

| 欄位 | 內容 |
|---|---|
| **要求** | 登入失敗次數計數與帳號鎖定（`LOGIN_MAX_LOCK_COUNT`／`LOGIN_USER_LOCK_TIME`）**必須**涵蓋所有登入路徑：本地密碼、LDAP／AD、雙因子驗證碼。任一路徑的失敗都要計入同一個計數 |
| **為什麼** | 只有本地路徑計數時，攻擊者拿登入頁對客戶的 AD 目錄猜密碼沒有上限——即使客戶目錄自己有鎖定政策，攻擊者借的是我們的入口 |
| **適用連線** | [C11](../connections/C11-api-to-ldap.html) api → 客戶 LDAP／AD、[C02](../connections/C02-frontdoor-to-api.html) 前門 → api（登入端點） |
| **對應威脅** | 無掃描實例（標準做法） |
| **驗證方式** | 以 LDAP 帳號連續輸錯密碼達鎖定次數，下一次即使密碼正確也必須被鎖；雙因子驗證碼連續猜錯達上限同樣鎖 |
| **來源** | 標準做法：C11 D5◇ |

## SR-01.4 OAuth 回呼同時驗連結與瀏覽器 {#sr-01-4}

| 欄位 | 內容 |
|---|---|
| **要求** | OAuth 回呼**必須**同時驗兩件事：「連結是真的」——狀態碼一次性、短效、不可猜、對不上即作廢；「回來的是發起的瀏覽器」——另一個綁定值只走發起者瀏覽器的 cookie（HttpOnly、路徑限回呼、SameSite=Lax），伺服器存雜湊、定時比對 |
| **為什麼** | 只驗狀態碼時，攻擊者把自己的授權連結轉寄給別人代按，別人的雲端硬碟就接進攻擊者的公司 |
| **適用連線** | [C04](../connections/C04-browser-to-api-drive-oauth.html) 瀏覽器 → api（OAuth 回呼） |
| **對應威脅** | C04 [T2 授權連結轉寄給別人代按](../connections/C04-browser-to-api-drive-oauth.html) ⚪ |
| **驗證方式** | 在瀏覽器 A 發起授權、拿到回呼網址後在瀏覽器 B 開啟（沒有 A 的 cookie），必須被拒且不換票；同一個回呼網址在 A 開第二次也必須被拒 |
| **來源** | 掃描驗證：C04 D2（實例 [M24-10](../STRIDE/S-spoofing.html#m24-10)） |

## SR-01.5 安全計數不得只存易失儲存 {#sr-01-5}

| 欄位 | 內容 |
|---|---|
| **要求** | 「歸零就等於解除保護」的計數——登入失敗、雙因子失敗、節流——**不得**只存在易失儲存（Redis）。要嘛落資料庫，要嘛儲存不可用時**拒絕而非放行** |
| **為什麼** | 計數只在 Redis 時，Redis 重啟（持久化刻意關掉）或拿到 Redis 帳密的人清掉 key，雙因子的猜測上限與節流當場解除 |
| **適用連線** | [C06](../connections/C06-api-to-redis.html) api → Redis |
| **對應威脅** | C06 [T2 拿到 Redis 帳密後歸零節流計數](../connections/C06-api-to-redis.html)（無掃描實例） |
| **驗證方式** | 雙因子連續猜錯 N−1 次後重啟 Redis，再猜一次必須仍被鎖（或計數已落 DB）；把 Redis 停掉，雙因子驗證與受節流的端點必須回錯誤而非放行 |
| **來源** | 標準做法：C06 D4◇ |

## SR-01.6 機器身分只認簽章通行證，每請求查撤銷 {#sr-01-6}

| 欄位 | 內容 |
|---|---|
| **要求** | 代理程式（機器）的身分**只認**後端簽發的簽章通行證，**不認**請求裡自報的機器編號。報到之外的每一支端點都要帶通行證；後端驗簽後只用證裡的機器編號；每個請求查一次撤銷／停用狀態；「回報結果」要比對「這張單是不是派給這台」。通行證效期與撤銷要配套——長效票必須搭配每請求查撤銷；撤銷後所有通道（含下載）同時失效 |
| **為什麼** | 後端只讀自報編號就相信時，站在任何連得到雲端的位置、不需帳號，照代理程式的格式打心跳說「我是客戶 X 的機器 Y」，就能領走派給 Y 的工作連同解密後的主機帳密，再替 Y 回報假結果 |
| **適用連線** | [C20](../connections/C20-agent-to-frontdoor.html) agent → 主系統前門（控制面） |
| **對應威脅** | C20 [T1 冒充代理程式——後端認不出對方是哪台機器](../connections/C20-agent-to-frontdoor.html) 🔴 |
| **驗證方式** | 不帶通行證打心跳必須 401；帶 A 機器的通行證、body 自報 B 機器編號，後端必須以 A 處理；管理員撤銷 A 後，A 的心跳／確認／回報／下載四條通道必須立即全部 403 |
| **來源** | 掃描驗證：C20 D1（實例 [M01-1](../STRIDE/S-spoofing.html#m01-1)）、C20 D6◇ |

## SR-01.7 機器報到比其他端點更嚴 {#sr-01-7}

| 欄位 | 內容 |
|---|---|
| **要求** | 報到（機器第一次取得身分）**必須**比其他端點更嚴：憑證申請檔的名稱必須等於本機指紋（擋「用自己的機器登記、名字填受害機」）；帶舊編號重新登記要附舊私鑰簽的持有證明；被撤銷的機器一律拒收、不再洗回正常。註冊 token **必須**有有效期與使用次數上限 |
| **為什麼** | 報到是身分的起點——報到放寬，後面每個請求驗得再嚴也是驗一個假身分。註冊 token 永不過期、不限次數時，token 一旦流出，任何人都能登記新機器進該客戶名下，直到管理員手動撤銷 |
| **適用連線** | [C20](../connections/C20-agent-to-frontdoor.html) agent → 主系統前門（報到端點） |
| **對應威脅** | C20 [T1 冒充代理程式](../connections/C20-agent-to-frontdoor.html) 🔴 |
| **驗證方式** | 以機器 A 的指紋產憑證申請、名稱填 B，報到必須被拒；用已撤銷的機器編號重新報到必須被拒；註冊 token 超過有效期或使用次數後報到必須被拒 |
| **來源** | 掃描驗證：C20 D2（實例 [M01-1](../STRIDE/S-spoofing.html#m01-1)） |

## SR-01.8 即時連線每個事件重注入伺服器端身分 {#sr-01-8}

| 欄位 | 內容 |
|---|---|
| **要求** | 即時連線（socket）**必須**在交握時驗身分（JWT），每個事件重新注入伺服器端身分，例外只回原發送者。署名與租戶**只取**伺服器端身分——畫面送來的 `user`、`tenant_id` 只能當顯示用途，或要經過隸屬檢查。每個帶房號的事件都反查資源、確認呼叫者是該專案的人——不能只守「加入」 |
| **為什麼** | 頻道只守「加入」時，其他事件不必先加入就能指定房號，猜一個房號就進去旁聽或廣播；署名由畫面送時，紀錄會記成別人改的 |
| **適用連線** | [C03](../connections/C03-frontdoor-to-socketio.html) 前門 → socketio |
| **對應威脅** | C03 [T1 頻道不查房間歸屬，猜一個房號就進去](../connections/C03-frontdoor-to-socketio.html) 🟠、C03 [T2 署名由畫面送上來](../connections/C03-frontdoor-to-socketio.html) 🟡 |
| **驗證方式** | 不帶 JWT 交握必須被拒；以專案 A 成員的身分對專案 B 的房號送 `update` 事件（不先 `join`）必須被拒；`update` 事件 body 帶別人的 `user`，落庫的署名必須是 JWT 裡的人 |
| **來源** | 掃描驗證：C03 D1（實例 [M05-7](../STRIDE/I-information-disclosure.html#m05-7)、[M24-9](../STRIDE/I-information-disclosure.html#m24-9)、[M06-2](../STRIDE/S-spoofing.html#m06-2)）、C03 D2（實例 [M05-9](../STRIDE/R-repudiation.html#m05-9)） |

---

*來源：[連線頁](../connections/C01-browser-to-frontdoor.html)的防線（D）；去重對照在 `requirements/_drafts/D-to-SR.md`。*
