管理器與通行密鑰)
Android 14 引入的憑據(jù)管理器框架,提供了一套用于管理用戶憑據(jù)的統(tǒng)一 API,可管理密碼、通行密鑰(FIDO2/WebAuthn)、聯(lián)合登錄令牌以及數(shù)字身份文檔。該框架用一套可插拔的系統(tǒng)服務(wù)取代了過去碎片化的自動填充服務(wù)與私有登錄 SDK,這套系統(tǒng)服務(wù)在請求應(yīng)用與憑據(jù)提供方應(yīng)用之間充當(dāng)中間媒介。本章完整梳理整套架構(gòu),從面向客戶端的CredentialManagerAPI,到系統(tǒng)服務(wù)、提供方會話、選擇器 UI,再到提供方側(cè)的CredentialProviderService。全部描述均基于 AOSP 真實源碼,對應(yīng)目錄:frameworks/base/services/credentials/與frameworks/base/core/java/android/credentials/。41.1 憑據(jù)管理器架構(gòu)41.1.1 問題背景在憑據(jù)管理器出現(xiàn)之前,憑據(jù)獲取需要依靠多個互不關(guān)聯(lián)的機制:機制局限性AccountManager僅管理賬號令牌;無標(biāo)準(zhǔn)化通行密鑰支持自動填充框架(AutofillService)設(shè)計目標(biāo)為視圖填充,并不適配現(xiàn)代憑據(jù)類型FIDO2 庫(Google Play 服務(wù))屬于私有實現(xiàn);AOSP 版本無法使用第三方密碼管理器每一款都需要單獨做集成適配應(yīng)用針對密碼、通行密鑰、聯(lián)合憑據(jù),需要調(diào)用各不相同的 API。用戶也需要分別對每一套機制進(jìn)行配置。41.1.2 設(shè)計目標(biāo)憑據(jù)管理器圍繞以下設(shè)計原則構(gòu)建:單一 API 入口:一次調(diào)用即可獲取任意類型憑據(jù)可插拔提供方:任意應(yīng)用均可注冊成為憑據(jù)提供方系統(tǒng)中介選擇:由系統(tǒng)控制憑據(jù)選擇器 UI兩階段協(xié)議:先執(zhí)行 “開始” 查詢,再在用戶選擇后完成最終處理按用戶隔離:Android 每一個用戶都擁有獨立的提供方配置41.1.3 高層架構(gòu)源文件位置:組件路徑CredentialManagerServiceframeworks/base/services/credentials/java/com/android/server/credentials/CredentialManagerService.javaCredentialManagerServiceImplframeworks/base/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.javaRequestSessionframeworks/base/services/credentials/java/com/android/server/credentials/RequestSession.javaProviderSessionframeworks/base/services/credentials/java/com/android/server/credentials/ProviderSession.javaRemoteCredentialServiceframeworks/base/services/credentials/java/com/android/server/credentials/RemoteCredentialService.javaCredentialManagerUiframeworks/base/services/credentials/java/com/android/server/credentials/CredentialManagerUi.javaCredentialProviderServiceframeworks/base/core/java/android/service/credentials/CredentialProviderService.javaCredentialframeworks/base/core/java/android/credentials/Credential.java41.1.4 核心抽象概念該框架引入多層抽象,讓系統(tǒng)可以通過統(tǒng)一協(xié)議處理各式各樣的憑據(jù)類型。Credential:帶類型的憑據(jù)數(shù)據(jù)容器。Credential類攜帶一個類型字符串以及存放數(shù)據(jù)的 Bundle。// 摘自 Credential.java public final class Credential implements Parcelable { public static final String TYPE_PASSWORD_CREDENTIAL = "android.credentials.TYPE_PASSWORD_CREDENTIAL"; private final String mType; private final Bundle mData; }不同憑據(jù)類型由字符串常量標(biāo)識:類型常量憑據(jù)種類TYPE_PASSWORD_CREDENTIAL用戶名?密碼對"androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL"通行密鑰(FIDO2/WebAuthn)"com.credman.IdentityCredential"數(shù)字身份文檔自定義字符串由提供方自定義的憑據(jù)CredentialOption:指定客戶端應(yīng)用請求的內(nèi)容。每一個選項包含類型、取回數(shù)據(jù)(Bundle)以及候選查詢數(shù)據(jù)。CredentialProviderInfo:已安裝憑據(jù)提供方的元數(shù)據(jù),包含組件名、能力(支持的憑據(jù)類型)、是否為系統(tǒng)提供方。41.1.5 兩階段協(xié)議system_server和憑據(jù)提供方之間采用兩階段通信,這是一項核心設(shè)計:階段 1:開始(查詢)階段 2:完成(Finalize)getCredential(request)客戶端發(fā)起請求onBeginGetCredential(beginRequest):系統(tǒng)向各個已啟用提供方發(fā)送開始獲取請求返回BeginGetCredentialResponse(憑據(jù)條目、認(rèn)證動作)系統(tǒng)展示憑據(jù)選擇器界面用戶選中某一條目,觸發(fā)該條目綁定的 PendingIntent,拉起提供方 Activity提供方讀取完整憑據(jù)返回GetCredentialResponse(credential)應(yīng)答回傳給客戶端階段 1(開始 / 查詢):系統(tǒng)向每一個啟用的提供方發(fā)送BeginGetCredentialRequest。提供方返回輕量化元數(shù)據(jù):描述可用憑據(jù)的憑據(jù)條目、提供方被鎖定時的認(rèn)證動作、可選遠(yuǎn)程條目。此階段不會傳輸任何真實憑據(jù)數(shù)據(jù)。階段 2(完成):用戶在系統(tǒng) UI 選中條目之后,系統(tǒng)觸發(fā)該條目附帶的PendingIntent。提供方的 Activity 讀取完整憑據(jù)(可能需要先生物識別校驗),并通過Activity.setResult()返回。這套兩階段設(shè)計具備重要安全特性:用戶明確選中之前,憑據(jù)原始數(shù)據(jù)不會加載進(jìn)內(nèi)存系統(tǒng)從不持有原始憑據(jù),僅負(fù)責(zé)轉(zhuǎn)發(fā)元數(shù)據(jù)提供方可要求先完成解鎖、生物認(rèn)證,才允許出示憑據(jù)數(shù)據(jù)41.1.6 服務(wù)注冊與發(fā)現(xiàn)憑據(jù)管理器在SystemServer啟動階段注冊成為系統(tǒng)服務(wù):// 摘自 CredentialManagerService.java @Override // from SystemService public void onStart() { publishBinderService(CREDENTIAL_SERVICE, new CredentialManagerServiceStub()); }服務(wù)名稱為Context.CREDENTIAL_SERVICE,獲取方式:CredentialManager cm = context.getSystemService(CredentialManager.class);41.2 CredentialManagerService41.2.1 服務(wù)繼承層級CredentialManagerService繼承自AbstractMasterSystemService,該框架模式用于管理多用戶子服務(wù)。類繼承關(guān)系:源碼文件:frameworks/base/services/credentials/java/com/android/server/credentials/CredentialManagerService.java41.2.2 構(gòu)造函數(shù)與配置解析器構(gòu)造函數(shù)完成基于系統(tǒng)設(shè)置的提供方解析邏輯綁定:// 摘自 CredentialManagerService.java(約130行) public CredentialManagerService(@NonNull Context context) { super( context, new SecureSettingsServiceNameResolver( context, Settings.Secure.CREDENTIAL_SERVICE, /* isMultipleMode= */ true), null, PACKAGE_UPDATE_POLICY_REFRESH_EAGER); mContext = context; }關(guān)鍵參數(shù)說明:參數(shù)作用Settings.Secure.CREDENTIAL_SERVICE存儲已啟用提供方組件名的配置鍵isMultipleMode=true支持同時啟用多個提供方,不同于自動填充的單提供方模型PACKAGE_UPDATE_POLICY_REFRESH_EAGER應(yīng)用包發(fā)生變更時立刻重新構(gòu)建提供方列表已啟用的提供方以冒號分隔的序列化ComponentName字符串,保存在Settings.Secure.CREDENTIAL_SERVICE。另一個配置項Settings.Secure.CREDENTIAL_SERVICE_PRIMARY記錄 “主提供方”(憑據(jù)創(chuàng)建時優(yōu)先選用)。41.2.3 用戶可配置提供方與系統(tǒng)提供方服務(wù)維護(hù)兩類提供方:系統(tǒng)發(fā)現(xiàn)系統(tǒng)提供方的代碼片段:// 摘自 CredentialManagerService.java private ListCredentialManagerServiceImpl constructSystemServiceListLocked( int resolvedUserId) { ListCredentialProviderInfo serviceInfos = CredentialProviderInfoFactory.getAvailableSystemServices( mContext, resolvedUserId, /* disableSystemAppVerificationForTests= */ false, new HashSet()); // ... 將每一個服務(wù)包裝為 CredentialManagerServiceImpl }41.2.4 請求會話管理所有正在進(jìn)行的憑據(jù)操作,以用戶為維度通過請求會話跟蹤:// 摘自 CredentialManagerService.java @GuardedBy("mLock") private final SparseArrayMapIBinder, RequestSession mRequestSessions = new SparseArray();SparseArray以用戶 ID 作為 key。同一個用戶可同時存在多個請求會話,會話使用IBinder令牌區(qū)分。請求發(fā)起時新增會話,會話完成或被取消時移除。private void addSessionLocked(int userId, RequestSession session) { synchronized (mLock) { MapIBinder, RequestSession sessions = mRequestSessions.get(userId); if (sessions == null) { sessions = new HashMap(); mRequestSessions.put(userId, sessions); } sessions.put(session.mRequestId, session); } }41.2.5 CredentialManagerServiceStub(Binder 接口)內(nèi)部類CredentialManagerServiceStub實現(xiàn)ICredentialManager.Stub,是 Binder 調(diào)用真正入口。主要方法:方法用途executeGetCredential()讀取已有憑據(jù)(密碼、通行密鑰)executeCreateCredential()創(chuàng)建 / 保存新憑據(jù)executePrepareGetCredential()兩步式讀?。合葴?zhǔn)備,再拉取憑據(jù)getCandidateCredentials()供自動填充模塊獲取候選憑據(jù)clearCredentialState()清除提供方側(cè)狀態(tài)(例如賬號登出)setEnabledProviders()配置生效的提供方getCredentialProviderServices()列出可用提供方isEnabledCredentialProviderService()檢查指定提供方是否啟用registerCredentialDescription()注冊憑據(jù)描述,用于匹配查詢41.2.6 獲取憑據(jù)完整流程executeGetCredential()方法負(fù)責(zé)調(diào)度完整讀取流程:1.請求校驗,創(chuàng)建會話// CredentialManagerServiceStub.executeGetCredential() final GetRequestSession session = new GetRequestSession( getContext(), mSessionManager, mLock, userId, callingUid, callback, request, constructCallingAppInfo(callingPackage, userId, request.getOrigin()), getEnabledProvidersForUser(userId), CancellationSignal.fromTransport(cancelTransport), timestampBegan); addSessionLocked(userId, session);2.初始化提供方會話:遍歷全部啟用提供方,為每一個具備處理能力的提供方創(chuàng)建ProviderGetSessionListProviderSession providerSessions = initiateProviderSessions(session, request.getCredentialOptions() .stream().map(CredentialOption::getType).collect(Collectors.toList()));3.調(diào)用提供方:ProviderSession.invokeSession()綁定遠(yuǎn)端提供方并調(diào)用onBeginGetCredential。4.聚合應(yīng)答:每一個提供方返回結(jié)果后觸發(fā)onProviderStatusChanged()。當(dāng)所有提供方都完成響應(yīng),并且至少存在一條可用憑據(jù):// GetRequestSession.onProviderStatusChanged() if (!isAnyProviderPending()) { if (isUiInvocationNeeded()) { getProviderDataAndInitiateUi(); } else { respondToClientWithErrorAndFinish( GetCredentialException.TYPE_NO_CREDENTIAL, "No credentials available"); } }5. UI 展示與用戶選擇:系統(tǒng) UI 展示聚合后的憑據(jù)列表;用戶選擇后onUiSelection()路由到對應(yīng)ProviderSession。6.返回最終憑據(jù):提供方PendingIntent解析完整憑據(jù),經(jīng)由onFinalResponseReceived()回調(diào)至客戶端。41.2.7 創(chuàng)建憑據(jù)流程創(chuàng)建憑據(jù)邏輯模式類似,使用CreateRequestSession與ProviderCreateSession。創(chuàng)建流程區(qū)別點:只創(chuàng)建一條憑據(jù),不是從多條現(xiàn)有憑據(jù)里選擇返回CreateEntry列表,每一項代表愿意保存憑據(jù)的一個提供方UI 高亮主提供方,主提供方由Settings.Secure.CREDENTIAL_SERVICE_PRIMARY指定41.2.8 權(quán)限模型憑據(jù)管理器強制校驗多項權(quán)限:權(quán)限使用場景CREDENTIAL_MANAGER_SET_ORIGIN設(shè)置自定義 origin(瀏覽器發(fā)起跨源請求場景)CREDENTIAL_MANAGER_S