議:PKCE 短期認證事務(wù)與一次性領(lǐng)取憑據(jù)實戰(zhàn)解析)
AI Agent人工智能大模型AI 應(yīng)用工具調(diào)用本地部署MCP ClientsAgent 記憶【免費下載鏈接】OperitThe most powerful AI agent and AI chat software on Android/Operit是一款A(yù)ndroid上能力最為強大、發(fā)展最久的AI Agent項目地址https://gitcode.com/gh_mirrors/op/Operit點擊查看免費下載導讀本文圍繞 Operit 項目中docs/TODO/github_oauth_broker/1_CloudBroker.md所定義的云端交換協(xié)議展開講解 Operit 如何把 GitHub OAuth 的授權(quán)碼交換從 Android 設(shè)備端遷移到api.operit.app的受保護 Worker并用 PKCE verifier、一次性領(lǐng)取憑據(jù)claim credential與短期認證事務(wù)構(gòu)建設(shè)備不接觸 client secret、敏感憑據(jù)不落地瀏覽器 URL的登錄鏈路。讀完本文你將掌握該協(xié)議的事務(wù)創(chuàng)建、完成回調(diào)、單次領(lǐng)取三階段設(shè)計以及它在 Android 與 Operit 2Rust CLI / Flutter兩代客戶端中的落地方式并可直接對照倉庫源碼復現(xiàn)每一條安全邊界。背景client secret 為何不能留在 APK 里在進入新協(xié)議之前先看舊實現(xiàn)為什么必須被替換。docs/TODO/github_oauth_broker/index.md記錄了這次改造的直接動因Android 客戶端曾將 GitHub OAuth client secret 寫入 BuildConfig并在設(shè)備上直接交換授權(quán)碼。該 secret 隨已發(fā)布 APK 分發(fā)不能繼續(xù)作為可信憑據(jù)。這是移動端 OAuth 的經(jīng)典悖論APK 可以輕易被反編譯編入 BuildConfig 的 client secret 等同于公開同時授權(quán)碼在設(shè)備端換取 token意味著任何能讀到 APK 的人都能冒充客戶端完成同樣的交換。舊協(xié)議里設(shè)備向 GitHub 直接提交 OAuth 授權(quán)碼和編入 APK 的 client secret見 1_CloudBroker.md因此被判定為不可繼續(xù)使用。改造目標被明確寫進 index.md新版 Android 只通過api.operit.app的受保護 Worker 完成 GitHub 授權(quán)碼交換新 APK 不再包含 client ID、client secret 或operit://OAuth 回調(diào)已發(fā)布的舊 APK 繼續(xù)使用原 OAuth App直至發(fā)布公告規(guī)定的遷移截止日新 OAuth App 的 client secret 只存在于 Cloudflare Worker secret。其中最后一條是整套協(xié)議的安全基石secret 只存在于云端設(shè)備端永遠拿不到。舊實現(xiàn)憑據(jù)落地的三種路徑1_CloudBroker.md與配套的 2_AndroidClient.md、3_Operit2Client.md 共同勾勒了改造前的全貌主要有三條路徑客戶端舊實現(xiàn)方式安全問題AndroidGitHub 登錄界面提供內(nèi)嵌 WebView 與外部瀏覽器兩條路徑應(yīng)用接收operit://github-oauth-callback后直接向 GitHub 交換 token授權(quán)碼經(jīng)自定義 scheme 回傳client secret 在設(shè)備上參與交換Rust CLIOperit 2使用 GitHub Device Flow并要求操作者設(shè)置 GitHub OAuth client ID 環(huán)境變量憑據(jù)進入環(huán)境變量流程與瀏覽器式授權(quán)割裂Flutter 市場頁Operit 2要求用戶創(chuàng)建并粘貼 GitHub Token用戶長期令牌被手動粘貼進客戶端風險面大舊 Android 實現(xiàn)的另一個隱患是自定義 scheme 與 Activity Intent 接管operit://github-oauth-callback這樣的 scheme 屬于全局可聲明標識存在被其他應(yīng)用搶占的風險同時瀏覽器回調(diào) URL 會真實攜帶授權(quán)碼。新協(xié)議的目標之一就是把授權(quán)碼、token 和領(lǐng)取憑據(jù)全部排除在瀏覽器回調(diào) URL 之外見 1_CloudBroker.md 的結(jié)果清單。新協(xié)議核心短期認證事務(wù)三階段新實現(xiàn)的協(xié)議骨架定義在 1_CloudBroker.mdWorker 創(chuàng)建短期認證事務(wù)生成 PKCE verifier 與一次性領(lǐng)取憑據(jù)。GitHub 回調(diào)由 Worker 接收并交換授權(quán)碼用戶 token 加密暫存后重定向到瀏覽器回調(diào) Host 預(yù)先注冊的完成地址Core 校驗完成鏈接后以領(lǐng)取憑據(jù) claim 一次記錄隨即刪除。把這段話拆解為三個階段的時序事務(wù)創(chuàng)建start客戶端向 Worker 提交自己準備的回調(diào)完成地址completion redirect URIWorker 創(chuàng)建短期認證事務(wù)生成 PKCE verifier 與一次性領(lǐng)取憑據(jù)delivery credential返回授權(quán)頁面 URL、事務(wù) ID、憑據(jù)與過期時間授權(quán)與回調(diào)complete用戶瀏覽器訪問 GitHub 授權(quán)頁授權(quán)碼回調(diào)由 Worker 接收并完成 PKCE 交換獲取的 token 在 Worker 側(cè)加密暫存隨后把瀏覽器重定向到客戶端預(yù)注冊的完成地址完成鏈接中只攜帶事務(wù)狀態(tài)單次領(lǐng)取claim客戶端Core校驗完成鏈接與當前事務(wù)匹配后用一次性領(lǐng)取憑據(jù) claim 一次Worker 返回 token 與用戶信息服務(wù)端記錄隨即刪除。這套設(shè)計同時滿足四個約束對應(yīng) 1_CloudBroker.md 的結(jié)果client secret 不離開 Cloudflare secret不落入 APK、不進入請求體授權(quán)碼、token 和領(lǐng)取憑據(jù)不出現(xiàn)在瀏覽器回調(diào) URL 中回調(diào) URL 只攜帶transactionId與status等非敏感參數(shù)完成通知不產(chǎn)生 Worker 輪詢請求客戶端一次 start、一次 claim沒有狀態(tài)輪詢新接口不改動舊客戶端使用的/market/v2/auth/github市場舊接口保持兼容。協(xié)議責任劃分3_Operit2Client.md 的協(xié)議責任進一步明確了邊界Worker 持有 OAuth client secret、生成 PKCE 和處理 GitHub 回調(diào)客戶端不包含 client ID 或 client secretFlutter 市場頁不持有 OAuth HTTP、平臺 Intent、EventChannel 或 loopback receiver只管理自己的可見 WebView 導航客戶端不向 Worker 反復查詢授權(quán)狀態(tài)Rust 解析和校驗 Broker 響應(yīng)并以單元測試固定協(xié)議契約。客戶端協(xié)議實現(xiàn)Broker Service 與 Coordinator云端 Worker 的行為無法在本倉庫直接查看后端位于獨立的marketWorker 工程但 Android 端的協(xié)議實現(xiàn)完整存在于本倉庫可以直接對照。1.GitHubOAuthBrokerService協(xié)議的兩個 HTTP 端點GitHubOAuthBrokerService.kt 是客戶端側(cè)與 Worker 通信的唯一入口基地址硬編碼為https://api.operit.app見 L144。它封裝了兩個請求startLogin(completionRedirectUri)L60-L82POST$BROKER_BASE_URL/oauth/github/start請求體為{completionRedirectUri: ...}解析返回GitHubOAuthBrokerStartResponse該響應(yīng)攜帶transactionId、deliveryCredential、authorizationUrl、completionRedirectUri與expiresAtL18-L25。客戶端拿到后即可展示授權(quán)頁同時本地私有保存領(lǐng)取憑據(jù)claimLogin(transactionId, deliveryCredential)L84-L128POST$BROKER_BASE_URL/oauth/github/claim請求體為{transactionId: ..., deliveryCredential: ...}。響應(yīng)status必須為complete否則拒絕L108-L112隨后解出accessToken、tokenType、scope、expiresIn、refreshToken與userL113-L122。兩個請求都使用 30 秒超時的 OkHttpClientL49-L53錯誤統(tǒng)一封裝為IllegalStateException并附帶 HTTP 狀態(tài)碼與響應(yīng)體便于排查L130-L136。響應(yīng)解析使用ignoreUnknownKeys的寬松 Json 配置L55-L58保證前后端字段演進時舊客戶端不因多余字段崩潰。2.GitHubOAuthCoordinator事務(wù)生命周期管理GitHubOAuthCoordinator.kt 是客戶端側(cè)的編排中樞startLogin(completionRedirectUri)L18-L33調(diào)用 Broker Service 創(chuàng)建事務(wù)并把transactionId、deliveryCredential、expiresAt通過GitHubAuthPreferences.saveActiveOAuthTransaction寫入本地 DataStore隨后返回事務(wù)供 UI 展示授權(quán)頁completeLogin(completionUri)L35-L82先校驗完成鏈接的transactionId與當前活動事務(wù)一致L39-L41否則直接失敗隨后按status參數(shù)分流——complete繼續(xù)領(lǐng)取、denied視為用戶取消并清事務(wù)、error透傳錯誤信息、其余視為非法狀態(tài)確認完成后調(diào)用claimLogin領(lǐng)取一次 token保存認證信息并清除活動事務(wù)cancelLogin()L84-L86用戶取消時清空活動事務(wù)避免遺留憑據(jù)被復用。值得注意的細節(jié)內(nèi)嵌登錄使用固定完成地址https://api.operit.app/oauth/github/completeL90而外部瀏覽器登錄則使用 loopback 臨時地址見下文兩者都會在 start 時預(yù)注冊給 Worker對應(yīng)文檔中重定向到瀏覽器回調(diào) Host 預(yù)先注冊的完成地址。3. 事務(wù)憑據(jù)的持久化GitHubAuthPreferencesGitHubAuthPreferences.kt 基于 DataStoregithub_auth_preferencesL19-L20管理全部 GitHub 認證狀態(tài)。與本次改造直接相關(guān)的設(shè)計有認證版本門檻REQUIRED_AUTH_VERSION 3L43isAuthSessionCurrentL90-L94要求本地會話的auth_version 3且授予 scope 覆蓋notifications,public_repo,user:email,read:userL42舊版本認證數(shù)據(jù)不會被新認證代碼繼續(xù)使用呼應(yīng) 2_AndroidClient.md 的認證版本升級舊 APK 數(shù)據(jù)不會被新版認證代碼繼續(xù)使用活動事務(wù)三鍵active_oauth_transaction_id、active_oauth_delivery_credential、active_oauth_expires_atL55-L57getActiveOAuthTransactionL245-L258在讀回時會檢查過期并自動清除杜絕過期憑據(jù)被 claim領(lǐng)取后清理saveAuthInfoL131-L165在寫入 token 的同時移除活動事務(wù)三鍵與服務(wù)端記錄隨即刪除形成兩端對稱的單次語義。Android 登錄 UI通用瀏覽器回調(diào)組件與雙路徑GitHubLoginDialog.kt 保留了內(nèi)嵌 WebView與外部瀏覽器兩條登錄路徑L50-L54 的GitHubLoginMode三態(tài)CHOOSER / EMBEDDED / EXTERNAL但底層機制全部替換。內(nèi)嵌路徑BrowserCallbackDialog通用組件內(nèi)嵌登錄不再持有 GitHub 協(xié)議邏輯而是復用通用組件 BrowserCallbackDialog.kt注釋明確寫著 Presents one host-owned browser flow and reports navigation to its registered callback destination。它只負責三件事加載authorizationUrlL109-L111在shouldOverrideUrlLoading與onPageStarted兩個時機捕獲導航L79-L95用matchesCallbackDestinationL189-L194按scheme / host / port / path 四元組匹配完成地址命中即回調(diào)onCompletion(uri)并stopLoading()處理超時與釋放expiresAt到期未完成則回調(diào)onFailureL113-L127releaseBrowserCallbackWebViewL197-L208在釋放時依次執(zhí)行停止加載、about:blank、清歷史、移除視圖、destroy()避免 WebView 泄漏。由于完成地址是https://api.operit.app/oauth/github/complete這樣的 https 地址而非自定義 scheme2_AndroidClient.md 的結(jié)果清單中的三項隨之成立刪除舊自定義 scheme、外部瀏覽器回調(diào)和 Activity Intent 接管瀏覽器回調(diào)組件不包含 GitHub 協(xié)議、token 或領(lǐng)取憑據(jù)刪除 Android 的 client ID 與 client secret BuildConfig 字段。外部路徑一次性 loopback 接收器外部瀏覽器登錄使用 GitHubOAuthLoopbackCallbackServer.kt 在127.0.0.1上臨時監(jiān)聽一個端口要求端口號 1024L98、L112-L114完成地址為http://127.0.0.1:port/oauth/github/completeL15-L22。awaitCompletionL24-L40只接受一次 GET 請求校驗路徑與完成地址一致L63-L66把查詢參數(shù)拼接回完成 URI 后返回 200其余請求返回 404。整個流程在 GitHubLoginDialog.kt 的GitHubExternalLoginDialog中L240-L301用withTimeout(remainingMillis)包裹超時即按登錄失敗處理finally中關(guān)閉服務(wù)器并清理事務(wù)——這與文檔完成通知不產(chǎn)生 Worker 輪詢請求的約束一致因為整個鏈路只有一次授權(quán)頁展示和一次完成回調(diào)。登出與會話隔離2_AndroidClient.md 還規(guī)定了一個易被忽略的體驗細節(jié)用戶明確退出 GitHub 登錄時應(yīng)用會清除自身 WebView 的 Cookie 和 WebStorage再刪除本地認證信息。下一次登錄不會靜默復用之前的 GitHub Web 會話這不會影響系統(tǒng)瀏覽器或 Chrome 的 GitHub 登錄狀態(tài)。即退出登錄需要同時清 WebView 會話與本地認證數(shù)據(jù)但作用域嚴格限定在應(yīng)用自身 WebView避免誤傷系統(tǒng)瀏覽器中用戶已登錄的 GitHub 賬號。Operit 2 客戶端遷移類型化服務(wù)替代命令字符串Operit 2Rust CLI 與 Flutter 市場的遷移方向與 Android 一致但多了一個架構(gòu)約束3_Operit2Client.md 要求兩端都調(diào)用 Core 的類型化GitHubOAuthBrokerServiceFlutter 使用生成的 Dart proxy、CLI 使用生成的 Rust proxy兩者都不傳遞市場認證命令字符串、不解析命令 stdout也不手寫 CoreLink 請求。具體分工應(yīng)用自己準備完成地址Core 用該地址調(diào)用 Worker 的/oauth/github/start并私有保存 delivery credential應(yīng)用展示授權(quán)頁并交回完成鏈接——CLI 使用臨時 loopback 并在終端打印授權(quán)鏈接Flutter 市場登錄對話框攔截其 WebView 的完成導航Core 只在收到與當前事務(wù)、目標地址都匹配的完成鏈接后 claim 一次并保存 Worker 返回的 GitHub tokenRust 側(cè)通過單元測試固定協(xié)議契約Rust 解析和校驗 Broker 響應(yīng)并以單元測試固定協(xié)議契約。這條遷移路徑的意圖很清晰舊實現(xiàn)里 CLI 依賴環(huán)境變量 client ID、Flutter 要求用戶粘貼 token、兩端以命令字符串與 stdout 解析方式對接市場認證都屬于憑據(jù)或協(xié)議細節(jié)外泄的脆弱設(shè)計新實現(xiàn)把協(xié)議收斂為類型化的 start/claim 調(diào)用憑據(jù)只在 Core 與 Worker 之間傳遞。結(jié)果清單與安全收益匯總綜合 1_CloudBroker.md 與 2_AndroidClient.md 的結(jié)果章節(jié)本次改造的驗收標準如下client secret 不離開 Cloudflare secret任何客戶端二進制與請求體中都不可見授權(quán)碼、token 和領(lǐng)取憑據(jù)不出現(xiàn)在瀏覽器回調(diào) URL 中回調(diào) URL 僅攜帶事務(wù) ID 與狀態(tài)完成通知不產(chǎn)生 Worker 輪詢請求全鏈路僅 start / claim 兩次 HTTP 往返新接口不改動舊客戶端使用的/market/v2/auth/github舊接口保持兼容直至遷移截止日Android 刪除舊自定義 scheme、外部瀏覽器回調(diào)和 Activity Intent 接管瀏覽器回調(diào)組件不含 GitHub 協(xié)議、token 或領(lǐng)取憑據(jù)Android 刪除 client ID 與 client secret 的 BuildConfig 字段認證版本升級到 3舊 APK 數(shù)據(jù)不會被新認證代碼繼續(xù)使用。從實現(xiàn)事實看這些收益都能在本倉庫的源碼中得到印證GitHubOAuthBrokerService的請求體只有completionRedirectUri/transactionId/deliveryCredentialGitHubOAuthBrokerService.kt沒有任何 client secret 字段完成鏈接校驗依賴transactionId與狀態(tài)參數(shù)GitHubOAuthCoordinator.kt倉庫中已搜不到operit://github-oauth-callback或 client secret BuildConfig 字段的殘留。部署順序與現(xiàn)狀index.md 的狀態(tài)章節(jié)記錄了該改造的推進節(jié)奏云端密鑰已配置、新 OAuth App 已創(chuàng)建、D1 遷移已應(yīng)用Worker 部署待后端現(xiàn)有未提交市場改動整理后執(zhí)行Operit 2 客戶端遷移進行中CLI 包當時的編譯問題與本協(xié)議無關(guān)。部署順序明確為后端先于 Android——這是合理的依賴順序新 APK 依賴 Worker 的/oauth/github/start與/oauth/github/claim端點云端必須先就緒舊客戶端才能繼續(xù)使用原 OAuth App 平穩(wěn)過渡到遷移截止日。對讀者而言若要在自己的項目里復刻這套方案最小可復制的骨架是一臺持有 client secret 的云端 Worker負責 PKCE 與授權(quán)碼交換、一個短期事務(wù)存儲帶過期與單次領(lǐng)取語義、客戶端側(cè)一次 start 一次 claim 的類型化調(diào)用以及一個只按 scheme/host/port/path 匹配完成地址的通用瀏覽器回調(diào)組件。Operit 的 GitHubOAuthBrokerService.kt 與 GitHubOAuthCoordinator.kt 提供了現(xiàn)成的參考實現(xiàn)。贊分享AI Agent人工智能大模型AI 應(yīng)用工具調(diào)用本地部署MCP ClientsAgent 記憶【免費下載鏈接】OperitThe most powerful AI agent and AI chat software on Android/Operit是一款A(yù)ndroid上能力最為強大、發(fā)展最久的AI Agent項目地址https://gitcode.com/gh_mirrors/op/Operit點擊查看免費下載相關(guān)推薦Operit GitHub OAuth 完成回調(diào)協(xié)議Worker 事務(wù)、應(yīng)用自持瀏覽器與一次性 Claim 的完整交付鏈路Operit GitHub OAuth 完成回調(diào)協(xié)議Worker 事務(wù)、應(yīng)用自持瀏覽器與一次性 Claim 的完整交付鏈路 本指南圍繞 Operit 倉庫中AI Agent人工智能大模型AI 應(yīng)用工具調(diào)用本地部署MCP ClientsAgent 記憶GUI 自動化MCP Toolbox 之 oceanbase-execute-sql在 OceanBase 上執(zhí)行 SQL 的 MCP 工具配置與實戰(zhàn)指南MCP Toolbox 之 oceanbase execute sql在 OceanBase 上執(zhí)行 SQL 的 MCP 工具配置與實戰(zhàn)指南 oceanbasAI Agent人工智能大模型AI 應(yīng)用工具調(diào)用本地部署MCP ClientsAgent 記憶GUI 自動化Open edX 認證憑證交換實戰(zhàn)auth_exchange 模塊的第三方 OAuth 接入與 Token 登錄實現(xiàn)Open edX 認證憑證交換實戰(zhàn)auth_exchange 模塊的第三方 OAuth 接入與 Token 登錄實現(xiàn) 導讀 本文以 Open edX 平臺后端教育上一篇深入解析mshumer/gpt-author項目AI自動生成小說全流程指南下一篇從0到1使用Google Workspace MCP Server構(gòu)建自動化郵件處理系統(tǒng)創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考