在穿戴式设备上进行身份验证:Credential Manager

Wear OS 应用可以独立运行,而无需配套应用。这意味着 Wear OS 应用从互联网访问数据时需要自行管理身份验证。但手表的屏幕尺寸较小,输入功能较少,这限制了 Wear OS 应用可以使用的身份验证选项。

本指南介绍了推荐用于 Wear OS 应用的身份验证方法:Credential Manager。

如需详细了解如何设计良好的登录体验,请参阅登录用户体验指南

初步注意事项

在开始实现之前,请考虑以下几点。

访客模式

不要对所有功能都要求进行身份验证,而是应尽可能在不要求用户登录的情况下为其提供更多功能。

用户发现并安装您的 Wear 应用时,可能以前并没有使用过该移动应用,因此他们可能没有账号,也可能不知道该应用提供哪些功能。确保访客模式功能准确展示应用的功能。

部分设备可能会保持解锁状态更长时间

在搭载 Wear OS 5 或更高版本的受支持设备上,系统会检测用户是否将设备戴在手腕上。如果用户关闭手腕检测功能,然后将设备从手腕上取下,系统会比以往更长时间地让设备保持解锁状态。

如果您的应用需要更高级别的安全性(例如在显示可能敏感或私密数据时),请先检查是否已启用手腕检测:

val wristDetectionEnabled =
        isWristDetectionAutoLockingEnabled(applicationContext)

如果此方法的返回值为 false,请提示用户在应用中登录账号,然后再显示特定于用户的内容。

Credential Manager

Credential Manager 是推荐用于在 Wear OS 上进行身份验证的 API。它为用户提供了更安全的环境,让用户无需连接配对的手机,也无需记住密码,即可在独立设置中登录 Wear OS 应用。

本文档概述了开发者需要了解的信息,以便使用 Credential Manager 解决方案及其托管的标准身份验证机制实现 Credential Manager 解决方案,这些机制包括:

  • 通行密钥
  • 密码
  • 联合身份(例如“使用 Google 账号登录”)

本指南还提供了有关如何将其他可接受的 Wear OS 身份验证方法(数据层令牌共享OAuth)迁移为 Credential Manager 的备用方法的说明,以及有关如何处理从现已废弃的独立 Google 登录按钮过渡到嵌入式 Credential Manager 版本的特殊说明。

Wear OS 上的通行密钥

强烈建议开发者在 Wear OS Credential Manager 实现中实现通行密钥。通行密钥是最终用户身份验证的新业界标准,为用户带来了多项显著优势

通行密钥更简单

  • 用户可以选择要登录的账号。他们无需输入用户名。
  • 用户可以使用设备的屏幕锁定功能进行身份验证。
  • 创建并注册通行密钥后,用户无需重新注册,即可顺畅切换到新设备并立即使用。

通行密钥更安全

  • 开发者只会将公钥保存到服务器,而不是保存密码,这意味着不法分子入侵服务器的价值会大大降低,并且在发生数据泄露时,需要进行的清理工作也会大大减少。
  • 通行密钥可防范钓鱼式攻击。通行密钥仅适用于已注册的网站和应用;由于浏览器或操作系统会处理验证,因此用户无法被诱骗在欺骗性网站上进行身份验证。
  • 通行密钥减少了发送短信的需要,从而提高了身份验证的性价比。

实现通行密钥

包含适用于所有实现类型的设置和指南。

设置

  1. 在应用模块的 build.gradle 文件中将目标 API 级别设置为 35:

    android {
        defaultConfig {
            targetSdkVersion(35)
        }
    }
    
  2. 使用 androidx.credentials 版本参考中的最新稳定版本,将以下代码行添加到应用或模块的 build.gradle 文件中。

    androidx.credentials:credentials:1.5.0
    androidx.credentials:credentials-play-services-auth:1.5.0
    

内置身份验证方法

由于 Credential Manager 是一个统一的 API,因此 Wear OS 的实现步骤与任何其他类型的设备相同。

请使用移动版说明开始使用通行密钥和密码支持功能,并实现相应支持。

向 Credential Manager 添加“使用 Google 账号登录”支持的步骤适用于移动开发,但在 Wear OS 上,这些步骤是相同的。如需了解此情形的特殊注意事项,请参阅从旧版“使用 Google 账号登录”功能改用新版部分。

请注意,由于无法在 Wear OS 上创建凭据,因此您无需实现移动版说明中提及的凭据创建方法。

备用身份验证方法

Wear OS 应用还接受以下两种身份验证方法:OAuth 2.0(任一变体)和移动身份验证令牌数据层共享。虽然这些方法在 Credential Manager API 中没有集成点,但您可以将其添加到 Credential Manager 的用户体验流程中,作为在用户关闭 Credential Manager 屏幕时使用的后备方法。

如需处理关闭 Credential Manager 屏幕的用户操作,请在 GetCredential 逻辑中捕获 NoCredentialException,然后转到您自己的自定义身份验证界面。

yourCoroutineScope.launch {
    try {
      val response = credentialManager.getCredential(activity, request)
      signInWithCredential(response.credential)
    } catch (e: GetCredentialCancellationException) {
      navigateToFallbackAuthMethods()
    }
}

然后,您的自定义身份验证界面可以提供登录用户体验指南中所述的任何其他可接受的身份验证方法。

数据层令牌共享

手机上的配套应用可以使用 Wearable Data Layer API 将身份验证数据安全地传输到 Wear OS 应用。可以采用消息或数据项的形式传输凭据。

这种类型的身份验证通常不需要用户执行任何操作。不过,请避免在没有通知用户其正在登录的情况下执行身份验证。您可以使用可关闭的屏幕通知用户,向他们显示正在从移动设备传输他们的账号凭据。

重要提示:您的 Wear OS 应用必须至少提供另外一种身份验证方法,因为此选项仅适用于安装了相应移动应用且与 Android 设备完成配对的手表。对于没有安装相应移动应用或所用 Wear OS 设备已与 iOS 设备配对的用户,您应提供替代的身份验证方法。

使用数据层从移动应用传递令牌,如以下示例所示:

val token = "..." // Auth token to transmit to the Wear OS device.
val dataClient: DataClient = Wearable.getDataClient(context)
val putDataReq: PutDataRequest = PutDataMapRequest.create("/auth").run {
    dataMap.putString("token", token)
    asPutDataRequest()
}
val putDataTask: Task<DataItem> = dataClient.putDataItem(putDataReq)

在 Wear OS 应用中监听数据更改事件,如以下示例所示:

val dataClient: DataClient = Wearable.getDataClient(context)
dataClient.addListener{ dataEvents ->
    dataEvents.forEach { event ->
        if (event.type == DataEvent.TYPE_CHANGED) {
            val dataItemPath = event.dataItem.uri.path ?: ""
            if (dataItemPath.startsWith("/auth")) {
                val token = DataMapItem.fromDataItem(event.dataItem).dataMap.getString("token")
                // Display an interstitial screen to notify the user that
                // they're being signed in.
                // Then, store the token and use it in network requests.
            }
        }
    }
}

如需详细了解如何使用穿戴式设备数据层,请参阅在 Wear OS 上发送和同步数据

使用 OAuth 2.0

Wear OS 支持两个基于 OAuth 2.0 的流程,后面几个部分对这些流程进行了介绍:

  • 采用 PKCE(用于代码交换的证明密钥)的授权代码授权,如 RFC 7636 中所定义
  • 设备授权 (DAG),如 RFC 8628 中所定义
用于代码交换的证明密钥 (PKCE)

如需有效地使用 PKCE,请使用 RemoteAuthClient。 然后,如需执行从 Wear OS 应用到 OAuth 提供方的身份验证请求,请创建 OAuthRequest 对象。此对象包含一个网址和一个 CodeChallenge 对象,该网址指向用于获取令牌的 OAuth 端点。

以下代码展示了创建身份验证请求的示例:

val request = OAuthRequest.Builder(this.applicationContext)
    .setAuthProviderUrl(Uri.parse("https://...."))
    .setClientId(clientId)
    .setCodeChallenge(codeChallenge)
    .build()

构建身份验证请求后,请使用 sendAuthorizationRequest() 方法将其发送到配套应用:

val client = RemoteAuthClient.create(this)
client.sendAuthorizationRequest(request,
    { command -> command?.run() },
    object : RemoteAuthClient.Callback() {
        override fun onAuthorizationResponse(
            request: OAuthRequest,
            response: OAuthResponse
        ) {
            // Extract the token from the response, store it, and use it in
            // network requests.
        }

        override fun onAuthorizationError(errorCode: Int) {
            // Handle any errors.
        }
    }
)

此请求会触发对配套应用的调用,然后,配套应用会在用户手机的网络浏览器中显示授权界面。OAuth 2.0 提供方会验证用户身份,并请求用户同意所请求的权限。系统会将响应发送到自动生成的重定向网址。

授权成功或失败后,OAuth 2.0 服务器会重定向到请求中指定的网址。如果用户批准了访问请求,响应中就会包含授权代码。如果用户未批准请求,响应中会包含错误消息。

响应采用查询字符串的形式,类似于以下示例之一:

  https://wear.googleapis.com/3p_auth/com.your.package.name?code=xyz
  https://wear.googleapis-cn.com/3p_auth/com.your.package.name?code=xyz

这会加载一个引导用户前往配套应用的页面。配套应用会验证响应网址,并使用 onAuthorizationResponse API 将响应传递给您的 Wear OS 应用。

然后,手表应用可用授权代码来换取访问令牌。

设备授权

使用设备授权时,用户需要在另一部设备上打开验证 URI。然后,授权服务器会提示他们批准或拒绝请求。

为了简化此过程,请使用 RemoteActivityHelper 在用户的配对移动设备上打开网页,如以下示例所示:

// Request access from the authorization server and receive Device Authorization
// Response.
val verificationUri = "..." // Extracted from the Device Authorization Response.
RemoteActivityHelper.startRemoteActivity(
    this,
    Intent(Intent.ACTION_VIEW)
        .addCategory(Intent.CATEGORY_BROWSABLE)
        .setData(Uri.parse(verificationUri)),
    null
)
// Poll the authorization server to find out if the user completed the user
// authorization step on their mobile device.

如果您有 iOS 应用,请使用通用链接在您的应用中拦截此 intent,而不要依赖浏览器向令牌授权。

停用旧版“使用 Google 账号登录”功能

Credential Manager 有一个专门用于“使用 Google 账号登录”按钮的集成点。以前,此按钮可添加到应用身份验证体验中的任意位置,但由于它已纳入 Credential Manager,因此旧选项现已废弃。

// Define a basic SDK check.
fun isCredentialManagerAvailable(): Boolean {
 return android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM
}

// Elsewhere in the code, use it to selectively disable the legacy option.
Button(
  onClick = {
    if (isCredentialManagerAvailable()) {
      Log.w(TAG, "Devices on API level 35 or higher should use
                  Credential Manager for Sign in with Google")
    } else {
      navigateToSignInWithGoogle()
    }},
  enabled = !isCredentialManagerAvailable(),
  label = { Text(text = stringResource(R.string.sign_in_with_google)) },
  secondaryLabel = { Text(text = "Disabled on API level 35+")
  }
)