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 應用程式,無須連結配對的手機,也不必記住密碼。
本文將概略說明開發人員需要哪些資訊,才能透過憑證管理工具解決方案,搭配其代管的標準驗證機制:
- 密碼金鑰
- 密碼
- 聯合身分識別 (例如使用 Google 帳戶登入)
本指南也提供如何將其他可接受的 Wear OS 驗證方法 (資料層權杖共用和 OAuth) 遷移為憑證管理工具備份的操作說明,以及如何處理從現已淘汰的獨立 Google 登入按鈕,改為內嵌憑證管理工具版本的特殊操作說明。
Wear OS 的密碼金鑰
我們強烈建議開發人員在 Wear OS Credential Manager 實作中導入密碼金鑰。密碼金鑰是使用者驗證的新業界標準,可為使用者帶來多項重要優點。
密碼金鑰更簡單
- 使用者可以選取要登入的帳戶。使用者不必輸入使用者名稱。
- 使用者可以使用裝置的螢幕鎖定功能進行驗證。
- 建立並註冊密碼金鑰後,使用者就能順暢切換至新裝置,並立即使用,無須重新註冊。
密碼金鑰更安全
- 開發人員只會將公開金鑰儲存至伺服器,而不會儲存密碼,這表示惡意人士入侵伺服器的價值大大降低,且在發生違規事件時,所需的清理工作也大幅減少。
- 密碼金鑰可防範網路釣魚。密碼金鑰只能用於使用者註冊的網站和應用程式,因此使用者無法在詐騙網站上受騙,因為瀏覽器或作業系統會負責驗證。
- 密碼金鑰可減少傳送簡訊的需求,讓驗證功能更具成本效益。
實作密碼金鑰
包含所有導入類型的設定和指南。
設定
在應用程式模組的 build.gradle 檔案中,將目標 API 級別設為 35:
android { defaultConfig { targetSdkVersion(35) } }
使用
androidx.credentials
版本參考資料中的最新穩定版,將以下行加入應用程式或模組的 build.gradle 檔案中。androidx.credentials:credentials:1.5.0 androidx.credentials:credentials-play-services-auth:1.5.0
內建驗證方法
Credential Manager 是統一 API,因此 Wear OS 的實作步驟與其他裝置類型相同。
請參閱行動版指引,瞭解如何開始使用並實作密碼金鑰和密碼支援功能。
將「使用 Google 帳戶登入」功能新增至 Credential Manager的步驟適用於行動裝置開發,但 Wear OS 的步驟也相同。如需瞭解這類情況的特殊考量事項,請參閱「從舊版「透過 Google 登入」服務轉換」一節。
請注意,由於無法在 Wear OS 上建立憑證,因此您不需要實作行動版操作說明中提到的憑證建立方法。
備用驗證方法
Wear OS 應用程式還支援兩種驗證方法:OAuth 2.0 (任一變體) 和手機驗證權杖資料層共用。雖然這些方法在 Credential Manager API 中沒有整合點,但如果使用者關閉 Credential Manager 畫面,您可以將這些方法納入 Credential Manager 的 UX 流程中,做為備用方法。
如要處理使用者關閉憑證管理工具畫面的動作,請在 GetCredential
邏輯中擷取 NoCredentialException
,並前往您自訂的驗證 UI。
yourCoroutineScope.launch {
try {
val response = credentialManager.getCredential(activity, request)
signInWithCredential(response.credential)
} catch (e: GetCredentialCancellationException) {
navigateToFallbackAuthMethods()
}
}
自訂驗證 UI 接著可提供登入使用者體驗指南中所述的任何其他可接受的驗證方法。
資料層權杖共用
透過 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 帳戶登入」按鈕整合點。先前這個按鈕可新增至應用程式驗證使用者體驗的任何位置,但由於已納入憑證管理工具,舊版選項現已淘汰。
// 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+")
}
)