Wear OS 應用程式可在沒有隨附應用程式的情況下獨立執行,這表示 Wear OS 應用程式在存取來自網際網路的資料時,需要自行管理驗證程序。但是手錶的小螢幕尺寸和不完整的輸入功能,使得 Wear OS 應用程式只能使用有限的驗證選項。
本指南說明如何使用建議的 Wear OS 應用程式驗證方法「憑證管理工具」。
如要進一步瞭解如何設計良好的登入體驗,請參閱登入使用者體驗指南。
初步考量
開始實作前,請先考量下列事項。
訪客模式
請勿採用一律要求驗證的做法,而是要在使用者不須登入的情況下盡可能提供最多功能。
使用者可能會發現並安裝您的 Wear 應用程式,但未使用相關行動應用程式,因此可能沒有帳戶,也不知道其中提供哪些功能。請確保訪客模式功能可正確展示應用程式的各項功能。
部分裝置可能會延長解鎖時間
在搭載 Wear OS 5 以上版本的支援裝置上,系統會偵測使用者是否將裝置戴在手腕上。如果使用者關閉腕帶偵測功能,然後取下手錶,系統會將裝置解鎖的時間延長。
如果應用程式需要更高層級的安全性 (例如顯示可能含有私密或私密資料時),請先檢查是否已啟用手腕偵測功能:
val wristDetectionEnabled =
isWristDetectionAutoLockingEnabled(applicationContext)
如果這個方法傳回 false
,請提示使用者登入應用程式帳戶,再顯示使用者專屬內容。
Credential Manager
如要在 Wear OS 驗證,建議使用 Credential Manager API。使用者不必連線配對手機,也不必記住密碼,就能在獨立設定中登入 Wear OS 應用程式,享有更安全的環境。
本文將說明開發人員如何透過標準驗證機制實作 Credential Manager 解決方案,包括:
- 密碼金鑰
- 密碼
- 聯合身分識別 (例如「使用 Google 帳戶登入」)
本指南也提供相關指引,說明如何遷移其他可接受的 Wear OS 驗證方法 (資料層權杖共用和 OAuth),做為 Credential Manager 的備份方法,以及如何處理從已淘汰的獨立 Google 登入按鈕,轉換為內嵌 Credential Manager 版本的特殊指引。
Wear OS 密碼金鑰
我們強烈建議開發人員在 Wear OS 認證管理工具實作中導入密碼金鑰。密碼金鑰是使用者驗證的全新業界標準,可為使用者帶來多項重大好處。
密碼金鑰更簡單
- 使用者可以選取要登入的帳戶。 他們不必輸入使用者名稱。
- 使用者可以透過裝置的螢幕鎖定進行驗證。
- 建立及註冊密碼金鑰後,使用者即可順暢切換至新裝置,並立即使用,無須重新註冊。
密碼金鑰更安全
- 開發人員只會將公開金鑰儲存到伺服器,而不是儲存密碼,這表示惡意行為者入侵伺服器後能取得的價值遠遠降低,而且發生違規事件時,需要清理的資料也大幅減少。
- 密碼金鑰可防範網路釣魚。密碼金鑰只能用於使用者註冊的網站和應用程式;由於驗證作業是由瀏覽器或作業系統處理,因此使用者不會在欺騙性網站上驗證身分。
- 密碼金鑰可減少傳送簡訊的需求,讓驗證程序更具成本效益。
實作密碼金鑰
包括所有導入類型的設定和指南。
設定
在應用程式模組的 build.gradle 檔案中,將目標 API 級別設為 35:
android { defaultConfig { targetSdkVersion(35) } }
在應用程式或模組的 build.gradle 檔案中加入下列程式碼行,並使用
androidx.credentials
版本參考資料中的最新穩定版。androidx.credentials:credentials:1.5.0 androidx.credentials:credentials-play-services-auth:1.5.0
內建驗證方法
由於憑證管理工具是統一的 API,因此 Wear OS 的實作步驟與其他裝置類型相同。
請參閱行動裝置操作說明,瞭解如何開始導入密碼金鑰和密碼支援功能。
在 Credential Manager 中新增「使用 Google 帳戶登入」支援的步驟是針對行動裝置開發作業,但 Wear OS 上的步驟相同。如要瞭解這類情況的特殊考量事項,請參閱「停用舊版『透過 Google 登入』功能」一節。
請注意,由於無法在 Wear OS 上建立憑證,因此您不需要實作行動裝置操作說明中提及的憑證建立方法。
備用驗證方法
Wear OS 應用程式還有兩種可接受的驗證方式:OAuth 2.0 (任一變體) 和 Mobile Auth Token Data Layer Sharing。雖然這些方法在 Credential Manager API 中沒有整合點,但可以納入 Credential Manager 的 UX 流程,做為使用者關閉 Credential Manager 畫面時的回退機制。
如要處理使用者關閉 Credential Manager 畫面的動作,請在 GetCredential
邏輯中擷取 NoCredentialException
,然後前往自訂驗證 UI。
yourCoroutineScope.launch {
try {
val response = credentialManager.getCredential(activity, request)
signInWithCredential(response.credential)
} catch (e: GetCredentialCancellationException) {
navigateToFallbackAuthMethods()
}
}
接著,自訂驗證 UI 即可提供登入 UX 指南所述的其他任何可接受的驗證方法。
共用資料層權杖
透過 Wearable Data Layer API,手機隨附應用程式可以安全將驗證資料傳送到 Wear OS 應用程式。以訊息或資料項目形式傳送憑證。
這類驗證通常不需使用者採取任何動作。不過請您避免在不通知使用者要登入的情況下進行驗證。您可以使用可關閉的畫面告知使用者,行動裝置正在轉移他們的帳戶。
重要事項:這種驗證方式僅限和 Android 配對的手錶使用,且須安裝對應的手機應用程式,因此您的 Wear OS 應用程式必須另外提供至少一種驗證方式。請為沒有對應手機應用程式的使用者,或是與 iOS 裝置配對的 Wear OS 裝置使用者,提供替代驗證方式。
透過手機應用程式使用資料層傳遞權杖,如以下範例所示:
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.
}
}
}
}
如要進一步瞭解如何使用 Wearable Data Layer,請參閱「在 Wear OS 上傳送資料和進行同步處理」。
使用 OAuth 2.0
Wear OS 支援兩種 OAuth 2.0 流程,如後續章節所述:
- 搭配 Proof Key for Code Exchange (PKCE) 使用 Authorization Code Grant (授權碼許可),如 RFC 7636 中定義
- 裝置授權許可 (DAG),如 RFC 8628 中定義
Proof Key for Code Exchange (PKCE)
如要有效利用 PKCE,請使用 RemoteAuthClient
。接著,如要透過 Wear OS 應用程式對 OAuth 提供者執行驗證要求,請建立 OAuthRequest
物件。這個物件包含用於取得權杖的 OAuth 端點網址和 CodeChallenge
物件。
下列程式碼範例說明如何建立驗證要求:
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.
}
}
)
這項要求會觸發對隨附應用程式的呼叫,在使用者的手機網路瀏覽器中顯示授權 UI。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 應用程式,請利用通用連結攔截應用程式意圖,不要使用瀏覽器驗證權杖。
改用舊版「使用 Google 帳戶登入」以外的服務
Credential Manager 設有「使用 Google 帳戶登入」按鈕的專屬整合點。先前,這個按鈕可新增至應用程式驗證 UX 的任何位置,但隨著按鈕納入憑證管理工具,舊版選項現已淘汰。
// 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+")
}
)