第 41 章:凭据管理器与通行密钥 Android 14 引入的凭据管理器框架,提供了一套用于管理用户凭据的统一 API,可管理密码、通行密钥(FIDO2/WebAuthn)、联合登录令牌以及数字身份文档。该框架用一套可插拔的系统服务取代了过去碎片化的自动填充服务与私有登录 SDK,这套系统服务在请求应用与凭据提供方应用之间充当中间媒介。本章完整梳理整套架构,从面向客户端的CredentialManagerAPI,到系统服务、提供方会话、选择器 UI,再到提供方侧的CredentialProviderService。全部描述均基于 AOSP 真实源码,对应目录:frameworks/base/services/credentials/与frameworks/base/core/java/android/credentials/。41.1 凭据管理器架构41.1.1 问题背景在凭据管理器出现之前,凭据获取需要依靠多个互不关联的机制:机制局限性AccountManager仅管理账号令牌;无标准化通行密钥支持自动填充框架(AutofillService)设计目标为视图填充,并不适配现代凭据类型FIDO2 库(Google Play 服务)属于私有实现;AOSP 版本无法使用第三方密码管理器每一款都需要单独做集成适配应用针对密码、通行密钥、联合凭据,需要调用各不相同的 API。用户也需要分别对每一套机制进行配置。41.1.2 设计目标凭据管理器围绕以下设计原则构建:单一 API 入口:一次调用即可获取任意类型凭据可插拔提供方:任意应用均可注册成为凭据提供方系统中介选择:由系统控制凭据选择器 UI两阶段协议:先执行 “开始” 查询,再在用户选择后完成最终处理按用户隔离:Android 每一个用户都拥有独立的提供方配置41.1.3 高层架构源文件位置:组件路径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 核心抽象概念该框架引入多层抽象,让系统可以通过统一协议处理各式各样的凭据类型。Credential:带类型的凭据数据容器。Credential类携带一个类型字符串以及存放数据的 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; }不同凭据类型由字符串常量标识:类型常量凭据种类TYPE_PASSWORD_CREDENTIAL用户名‑密码对"androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL"通行密钥(FIDO2/WebAuthn)"com.credman.IdentityCredential"数字身份文档自定义字符串由提供方自定义的凭据CredentialOption:指定客户端应用请求的内容。每一个选项包含类型、取回数据(Bundle)以及候选查询数据。CredentialProviderInfo:已安装凭据提供方的元数据,包含组件名、能力(支持的凭据类型)、是否为系统提供方。41.1.5 两阶段协议system_server和凭据提供方之间采用两阶段通信,这是一项核心设计:阶段 1:开始(查询)阶段 2:完成(Finalize)getCredential(request)客户端发起请求onBeginGetCredential(beginRequest):系统向各个已启用提供方发送开始获取请求返回BeginGetCredentialResponse(凭据条目、认证动作)系统展示凭据选择器界面用户选中某一条目,触发该条目绑定的 PendingIntent,拉起提供方 Activity提供方读取完整凭据返回GetCredentialResponse(credential)应答回传给客户端阶段 1(开始 / 查询):系统向每一个启用的提供方发送BeginGetCredentialRequest。提供方返回轻量化元数据:描述可用凭据的凭据条目、提供方被锁定时的认证动作、可选远程条目。此阶段不会传输任何真实凭据数据。阶段 2(完成):用户在系统 UI 选中条目之后,系统触发该条目附带的PendingIntent。提供方的 Activity 读取完整凭据(可能需要先生物识别校验),并通过Activity.setResult()返回。这套两阶段设计具备重要安全特性:用户明确选中之前,凭据原始数据不会加载进内存系统从不持有原始凭据,仅负责转发元数据提供方可要求先完成解锁、生物认证,才允许出示凭据数据41.1.6 服务注册与发现凭据管理器在SystemServer启动阶段注册成为系统服务:// 摘自 CredentialManagerService.java @Override // from SystemService public void onStart() { publishBinderService(CREDENTIAL_SERVICE, new CredentialManagerServiceStub()); }服务名称为Context.CREDENTIAL_SERVICE,获取方式:CredentialManager cm = context.getSystemService(CredentialManager.class);41.2 CredentialManagerService41.2.1 服务继承层级CredentialManagerService继承自AbstractMasterSystemService,该框架模式用于管理多用户子服务。类继承关系:源码文件:frameworks/base/services/credentials/java/com/android/server/credentials/CredentialManagerService.java41.2.2 构造函数与配置解析器构造函数完成基于系统设置的提供方解析逻辑绑定:// 摘自 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; }关键参数说明:参数作用Settings.Secure.CREDENTIAL_SERVICE存储已启用提供方组件名的配置键isMultipleMode=true支持同时启用多个提供方,不同于自动填充的单提供方模型PACKAGE_UPDATE_POLICY_REFRESH_EAGER应用包发生变更时立刻重新构建提供方列表已启用的提供方以冒号分隔的序列化ComponentName字符串,保存在Settings.Secure.CREDENTIAL_SERVICE。另一个配置项Settings.Secure.CREDENTIAL_SERVICE_PRIMARY记录 “主提供方”(凭据创建时优先选用)。41.2.3 用户可配置提供方与系统提供方服务维护两类提供方:系统发现系统提供方的代码片段:// 摘自 CredentialManagerService.java private ListCredentialManagerServiceImpl constructSystemServiceListLocked( int resolvedUserId) { ListCredentialProviderInfo serviceInfos = CredentialProviderInfoFactory.getAvailableSystemServices( mContext, resolvedUserId, /* disableSystemAppVerificationForTests= */ false, new HashSet()); // ... 将每一个服务包装为 CredentialManagerServiceImpl }41.2.4 请求会话管理所有正在进行的凭据操作,以用户为维度通过请求会话跟踪:// 摘自 CredentialManagerService.java @GuardedBy("mLock") private final SparseArrayMapIBinder, RequestSession mRequestSessions = new SparseArray();SparseArray以用户 ID 作为 key。同一个用户可同时存在多个请求会话,会话使用IBinder令牌区分。请求发起时新增会话,会话完成或被取消时移除。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 接口)内部类CredentialManagerServiceStub实现ICredentialManager.Stub,是 Binder 调用真正入口。主要方法:方法用途executeGetCredential()读取已有凭据(密码、通行密钥)executeCreateCredential()创建 / 保存新凭据executePrepareGetCredential()两步式读取:先准备,再拉取凭据getCandidateCredentials()供自动填充模块获取候选凭据clearCredentialState()清除提供方侧状态(例如账号登出)setEnabledProviders()配置生效的提供方getCredentialProviderServices()列出可用提供方isEnabledCredentialProviderService()检查指定提供方是否启用registerCredentialDescription()注册凭据描述,用于匹配查询41.2.6 获取凭据完整流程executeGetCredential()方法负责调度完整读取流程:1.请求校验,创建会话// 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.初始化提供方会话:遍历全部启用提供方,为每一个具备处理能力的提供方创建ProviderGetSessionListProviderSession providerSessions = initiateProviderSessions(session, request.getCredentialOptions() .stream().map(CredentialOption::getType).collect(Collectors.toList()));3.调用提供方:ProviderSession.invokeSession()绑定远端提供方并调用onBeginGetCredential。4.聚合应答:每一个提供方返回结果后触发onProviderStatusChanged()。当所有提供方都完成响应,并且至少存在一条可用凭据:// GetRequestSession.onProviderStatusChanged() if (!isAnyProviderPending()) { if (isUiInvocationNeeded()) { getProviderDataAndInitiateUi(); } else { respondToClientWithErrorAndFinish( GetCredentialException.TYPE_NO_CREDENTIAL, "No credentials available"); } }5. UI 展示与用户选择:系统 UI 展示聚合后的凭据列表;用户选择后onUiSelection()路由到对应ProviderSession。6.返回最终凭据:提供方PendingIntent解析完整凭据,经由onFinalResponseReceived()回调至客户端。41.2.7 创建凭据流程创建凭据逻辑模式类似,使用CreateRequestSession与ProviderCreateSession。创建流程区别点:只创建一条凭据,不是从多条现有凭据里选择返回CreateEntry列表,每一项代表愿意保存凭据的一个提供方UI 高亮主提供方,主提供方由Settings.Secure.CREDENTIAL_SERVICE_PRIMARY指定41.2.8 权限模型凭据管理器强制校验多项权限:权限使用场景CREDENTIAL_MANAGER_SET_ORIGIN设置自定义 origin(浏览器发起跨源请求场景)CREDENTIAL_MANAGER_S