第11章封面圖:Section 11 動態工具權限與呼叫管制

這章要學會什麼

  • 寫一個共用的授權判斷服務,讓「列出工具」跟「呼叫工具」用同一套規則判斷會員能不能用某個工具。
  • 在每一次呼叫工具時,都重新檢查通行證的權限範圍、會員狀態、使用權狀態與期限,而且說明為什麼這裡不能用快取結果。
  • 用一個原子性的資料庫更新指令做出「額度預留」,實測 20 個同時發生的呼叫搶最後 10 次額度,剛好只有 10 個成功。
  • 讓「從未獲准使用」的工具,跟「根本不存在」的工具,回應內容完全一模一樣。
  • 加上「每分鐘呼叫次數上限」的保護,並且在撤銷使用權時,讓工具清單的快取立刻失效。

先備知識

要先完成第 10 章:/mcp 已經用正式通行證驗證,會員身分由對應表查出來。要記得第 7 章的使用權狀態(有效/已撤銷/已到期)跟後臺撤銷功能。

觀念白話講

為什麼每一次呼叫都要重新檢查

通行證簽發後 10 分鐘內都有效,AI 助理也可能把工具清單記在自己那邊。如果只在簽發通行證,或列工具的那一刻檢查一次,這段時間內發生的事平台完全擋不住:

這段時間可能發生只檢查一次的後果本章的做法
管理員撤銷使用權舊通行證還能繼續用滿 10 分鐘每次呼叫都重新查資料庫裡的使用權
會員被停權已經發出的通行證照樣可以用每次都重新檢查會員是否啟用
使用權到期過了期限還能繼續呼叫每次都重新比對有效期間
額度用完可以超量呼叫每次呼叫前都原子地預留一次額度
版本被下架呼叫已經下架的服務只找目前狀態是已發布的版本

通行證回答的是「你是誰、哪個 AI 助理」;能不能用某個工具,是平台資料庫此時此刻的即時狀態,每一次都要重新看一遍。

一次呼叫,要通過的檢查順序

呼叫工具在真正送出上游之前的檢查順序:①工具存在且版本已發布→②通行證有正確的權限範圍→③會員沒有被停權→④使用權有效且在期限內→⑤預留一次額度→⑥才交給安全閘道去真正呼叫。
圖 11-1 呼叫工具在真正送出上游之前的檢查順序:①工具存在且版本已發布→②通行證有正確的權限範圍→③會員沒有被停權→④使用權有效且在期限內→⑤預留一次額度→⑥才交給安全閘道去真正呼叫。

其中①③④如果不通過,會被拒絕但不計入額度——因為根本沒有用到任何上游資源;而⑤跟⑥都通過之後才會真正扣一次額度,就算後面上游失敗了,這次額度也不會退還。

「從未獲准」要跟「不存在」回應一模一樣

如果會員從沒申請過某個服務,呼叫時系統回「你沒有權限使用這個工具」,等於告訴他這個工具確實存在,只是他不能用——攻擊者可以拿這個線索,一個一個猜工具名稱,逐步拼湊出平台上到底有哪些服務。所以「從未獲准」跟「這個工具根本不存在」對外必須顯示完全相同的協定層級錯誤訊息。差別只在平台自己的內部紀錄裡:「從未獲准」記的是明確的原因代碼,讓管理員事後查得出來「有人在嘗試猜測工具名稱」;如果是「曾經獲准、後來被撤銷」,則會直接把撤銷這個原因告訴呼叫者——因為那本來就是他自己知道的事,講清楚反而比較好處理。

額度:怎麼確保「兩個人不會同時搶到最後一次」

原子的額度預留:①送出一個「更新且同時滿足條件」的資料庫指令→②看更新到幾筆?→有更新到就③預留成功;沒有更新到就④檢查今天這一列是否已經存在→已存在就⑤已用完,不存在就⑥新增第一筆並回到步驟①再確認一次。
圖 11-2 原子的額度預留:①送出一個「更新且同時滿足條件」的資料庫指令→②看更新到幾筆?→有更新到就③預留成功;沒有更新到就④檢查今天這一列是否已經存在→已存在就⑤已用完,不存在就⑥新增第一筆並回到步驟①再確認一次。

最直覺、但錯誤的寫法是:先讀出目前已用次數,如果小於額度就加一再存回去。兩個同時發生的請求可能都讀到「還剩最後 1 次」,兩邊都判斷「可以用」、都寫回去,結果額度就被突破了。正確做法是讓「比較」跟「加一」發生在同一個資料庫指令裡:資料庫在真正更新那一列的當下會自動鎖住它,第二個請求必須等第一個做完,這時候它看到的已經是新的數字,條件不成立、影響筆數是 0——程式只要看「這次真的更新到幾筆」就能判斷預留成功還是已經用完,不需要自己額外處理鎖定的細節。

清單可以快取,但「呼叫」永遠不能信任快取

「列出工具」這個動作容許使用短時間快取(AI 助理常常反覆問這件事,每次都查資料庫太浪費),核准或撤銷時會立刻清掉快取,所以正常操作下清單幾乎是即時的。但「呼叫工具」絕對不使用快取——萬一使用權是被別的途徑直接改掉的(例如直接改資料庫,沒有經過正常的撤銷功能),快取可能要等一段時間才會更新,這時候清單「晚一點才消失」只是顯示上的小問題,但「呼叫」這個動作必須在當下就被擋住,不能因為快取還沒過期就放行。額外還加上每位會員每分鐘的呼叫次數上限,防止失控的 AI 迴圈瞬間用光額度或拖垮上游服務。

老師示範做了什麼

示範情境:一位會員只看得到自己獲准的工具;猜測別的工具名稱得到跟不存在完全相同的回應;20 個同時發生的呼叫搶最後 10 次額度;撤銷使用權後,就算沒有換掉手上的通行證,也立刻被拒絕;停權、權限範圍不足、呼叫太密集也都會被擋下。

後臺使用權管理頁新增的「今日已用/額度」欄:某筆使用權顯示「已用滿」的紅字提示,就是並行搶額度測試的結果;另一筆使用權仍然有效,右側保留撤銷欄位。
圖 11-3 後臺使用權管理頁新增的「今日已用/額度」欄:某筆使用權顯示「已用滿」的紅字提示,就是並行搶額度測試的結果;另一筆使用權仍然有效,右側保留撤銷欄位。

老師實測把某筆使用權的今日已用次數手動設成「只剩 10 次額度」,然後同時發送 20 個呼叫請求,結果剛好是10 個成功、10 個額度用完,一次都沒有多。接著示範撤銷使用權:手上的通行證完全沒有換過,撤銷後同一張通行證的「列出工具」立刻變成 0 個,呼叫也立刻得到「使用權已被撤銷」;停權該會員後同樣立刻被擋下;把通行證的權限範圍改掉,一樣立刻被拒絕;把每分鐘上限調成一個很小的數字,連續呼叫到第幾次就開始被拒絕,換一位不同的會員呼叫則完全不受影響,各自獨立計算。

自己動手的步驟

步驟一:共用的授權判斷服務

寫一個服務,讓「列出工具」跟「呼叫工具」都呼叫同一套規則:先確認通行證有正確的權限範圍、會員沒有被停權,再一定查資料庫、不查快取取得這個版本的使用權清單;完全沒有使用權就回傳「當作沒有這個工具」;有使用權但目前狀態不能用(已撤銷、已到期、尚未生效),回傳最新一筆的具體原因;都通過才進入額度預留這一步。

步驟二:原子的額度預留

用一個「更新且同時比較條件」的資料庫指令去嘗試預留:更新到 1 筆代表成功;沒更新到的話,先確認今天是不是已經有這一列資料,有的話代表已達上限;沒有的話代表今天第一次呼叫,新增一筆初始值。如果剛好兩個請求同時走到「新增第一筆」這一步,其中一個會撞到唯一性限制而失敗,這時候只要重新跑一次前面的更新指令即可,因為另一個請求已經把這一列建好了。

步驟三:處理常式與回應格式一致化

呼叫工具時,先問共用授權服務要不要放行;如果判定為「當作沒有這個工具」,紀錄照樣要寫,但回應要跟「協定層級找不到工具」完全相同;判定為放行才真正交給安全閘道呼叫上游,並且把用到的使用權編號一起記進紀錄裡;被拒絕的話,經過同一個地方寫紀錄,但不會真的送出請求。核准跟撤銷使用權的動作結束時,都要記得清除這位會員的工具清單快取,讓「列出工具」能立刻反映最新狀態。

步驟四:每分鐘呼叫次數上限

依「已經對應出來的會員編號」分別計算每分鐘的呼叫次數,超過上限直接回傳「請求過於頻繁」,不排隊、不等待——因為如果是失控的 AI 迴圈,應該讓它立刻知道被擋下,而不是越排越久。這個檢查要放在「已經驗證出使用者身分」之後才生效,否則所有沒登入的請求會被誤判成同一個匿名使用者、共用同一個額度。

常見錯誤

症狀原因怎麼處理
設定每分鐘上限為 5 次,測試腳本卻在第 5 次就被擋下取得通行證的腳本本身最後也送出了一次「列出工具」,已經先用掉一次額度這是正確行為:/mcp 的每一種請求(包含列出工具)都算
直接用資料庫工具把使用權改成已撤銷,工具清單卻沒有立刻消失沒有經過正常的撤銷功能,快取沒有被清除,要等快取自然過期一律用後臺撤銷功能;「呼叫」動作本來就不受快取影響,安全性不受影響
並行測試時全部得到「連不上上游」,而且額度已經被扣掉了示範用的上游服務沒有啟動;額度預留發生在真正呼叫上游之前,上游失敗不會退還啟動示範服務;這正是「上游失敗也計次」的既定規則

怎麼驗收+反向驗證

  1. 越權猜測工具名稱仍然無法呼叫:呼叫一個從未獲准的工具,跟呼叫一個根本不存在的工具,兩者回應完全相同;但呼叫紀錄裡能查到明確的原因代碼。
  2. 撤銷後使用舊通行證也會被拒絕:取得通行證後撤銷使用權,同一張通行證立刻被擋下,工具清單立刻變成 0 個。
  3. 兩個同時發生的呼叫不能突破最後一筆額度:把今天已用次數設成「額度減 1」,同時發送兩個呼叫,一個成功、一個「額度用完」;加碼測試多個同時發生的呼叫搶最後幾次額度,成功的數量剛好等於剩餘額度,一次都不多。
  4. 反向驗證:停權後被擋下;權限範圍不足被擋下;每分鐘上限測試中,第一次超過上限的請求得到「請求過於頻繁」。
← 上一章 回課程地圖 下一章 →