前言

串接 Google Sign 不外乎在 Google Cloud 進行設定、在 APP 引入 SDK、APP 與 Server API 串接。

本篇文章紀錄 Android APP 設定/實現的步驟。


另外需要特別注意的地方是,我們一共會在 Google Cloud 設置3組 OAuth2.0用戶端ID。

Google Cloud 設定

首先,

需要在 Google Cloud Console 新增專案


接著,

在側邊欄「API和服務」 -> 「OAuth 同意畫面」填入相關設定


接著,

在 「憑證」 -> 「OAuth 同意畫面」 建立 OAuth 用戶端 ID


要建立的用戶端 ID 有2個

1. Android

2. 網頁應用程式


也許會認為為何 Android APP 需要建立網頁應用程式,這是因為在登入時,需要填入「ServerClientID」才能獲取 GoogleIDToken,以利上傳至 Server。


另外,在建立 Android 時,會需要輸入 SHA-1 憑證指紋:

SHA1憑證指紋

在 Google Cloud Console 就可以看到產生指紋的提示

 

keytool -keystore path-to-debug-or-production-keystore -list -v

 

但是 keystore 還是得自己手動產生:

建立 keystore

回到 Android Studio,點擊上方「Build」 -> 「Generate Signed Bundle / APK...」,可以點擊「Create new...」產生一個新的 Key store,


資訊填妥之後,也會產生一個「private_key.pepk」檔案。


根據先前的經驗,這些資訊請妥善保存,否則一但上架後,過了很久要發布新版本,如果檔案不見就 GG了。

但是聯絡 Google Developer 客服,客服人員也會協助處理。


接著,就可以透過剛剛產生好的 keystore 產生 SHA-1 憑證指紋了 (執行 keytool 指令)

繼續在 Android APP 撰寫程式

在 Google Cloud Console 完成設定之後,回到 Android Studio 繼續寫 Code...


只需要依照官方文件的引導,在 build.gradle 的 dependencies 引入

 

implementation 'com.google.android.gms:play-services-auth:20.4.1'
 

接著撰寫程式即可

 

這邊偷懶,直接貼上範例 code

package me.codus.sample
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.activity.result.IntentSenderRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import com.google.android.gms.auth.api.identity.GetSignInIntentRequest
import com.google.android.gms.auth.api.identity.Identity
import com.google.android.gms.auth.api.identity.SignInClient
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.common.api.ApiException
import com.google.android.gms.common.api.CommonStatusCodes
import java.util.*
class LoginActivity: AppCompatActivity() {
val TAG = javaClass.simpleName
private lateinit var binding: ActivityLoginBinding
private lateinit var oneTapClient: SignInClient
private lateinit var getSignInIntentRequest: GetSignInIntentRequest
private val serverClientID = "{{Your Web Client ID}}"
private val googleSignInLauncher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result ->
Log.i(TAG, "google login get account info result.resultCode:${result.resultCode}")
if (result.resultCode == Activity.RESULT_CANCELED) {
binding.loading.root.end()
return@registerForActivityResult
}
try {
val credential = oneTapClient.getSignInCredentialFromIntent(result.data)
Log.i(TAG, "google login get account info id:${credential.id}")
Log.i(TAG, "google login get account info googleIdToken:${credential.googleIdToken}")
Log.i(TAG, "google login get account info password:${credential.password}")
Log.i(TAG, "google login get account info givenName:${credential.givenName}")
Log.i(TAG, "google login get account info familyName:${credential.familyName}")
Log.i(TAG, "google login get account info displayName:${credential.displayName}")
Log.i(TAG, "google login get account info profilePictureUri:${credential.profilePictureUri}")
val idToken = credential.googleIdToken
when {
idToken != null -> {
// Got an ID token from Google. Use it to authenticate
// with your backend.
Log.d(TAG, "Got ID token.")
login(idToken)
}
else -> {
alertError("發生預期外錯誤,沒有取得資訊")
// Shouldn't happen.
Log.d(TAG, "No ID token or password!")
}
}
} catch (e: ApiException) {
when (e.statusCode) {
CommonStatusCodes.CANCELED -> {
Log.d(TAG, "One-tap dialog was closed.")
// Don't re-prompt the user.
}
CommonStatusCodes.NETWORK_ERROR -> {
Log.d(TAG, "One-tap encountered a network error.")
// Try again or just ignore.
alertError(e.localizedMessage)
Log.d(TAG, e.localizedMessage)
}
else -> {
Log.d(TAG, "Couldn't get credential from result. (${e.localizedMessage})")
alertError(e.localizedMessage)
}
}
}
binding.loading.root.end()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityLoginBinding.inflate(layoutInflater)
setContentView(binding.root)
initGoogleSignIn()
binding.buttonLogin.setOnClickListener {
signInGoogle()
}
}
private fun initGoogleSignIn() {
oneTapClient = Identity.getSignInClient(this)
getSignInIntentRequest = GetSignInIntentRequest.builder()
.setNonce(Date().time.toString())
.setServerClientId(serverClientID)
.build()
}
private fun signInGoogle() {
oneTapClient
.getSignInIntent(getSignInIntentRequest)
.addOnSuccessListener { pendingIntent ->
googleSignInLauncher.launch(IntentSenderRequest.Builder(pendingIntent).build())
}
.addOnFailureListener {
// No saved credentials found. Launch the One Tap sign-up flow, or
// do nothing and continue presenting the signed-out UI.
alertError(it.localizedMessage)
Log.d(TAG, it.localizedMessage)
}
}
private fun login(idToken: String) {
// TODO call API
}
}

上架到 Google Play 後

當一切就緒,登入功能運行無誤,APP上傳到 Google Play 封閉測試/發布上架後,

發現登入居然失敗了!

可能是使用者點擊登入後就沒反應,

也可能是出現錯誤代碼13。

應用程式簽署

當 APP 上傳到 Google Play 之後,

Google Play 會產生一個新的簽名,

可以在 Google Play 找到:

Google Play -> 該應用程式 -> 設定 -> 應用程式完整性 -> 應用程式簽署


這時候只要將該 SHA-1 憑證指紋,再次添加到 Google Cloud 即可

google play 應用程式簽署金鑰憑證

到 Google Play 查看應用程式簽署金鑰憑證

google cloud 再次新增 OAuth client ID

在 Google Cloud 建立 OAuth client ID

參考連結

Android 上的 One Tap 登入總覽 - Google Identity Android Google登录和facebook登录接入 - 掘金 Google Sign In 素材 - 登入品牌規範 Google sign in not working after publishing in play store - StackOverflow
FBLINETwitterLinkIn
回部落格