1、google平台注册应用

https://console.developers.google.com/auth/clients

选择Android,填入应用包名

填入 SHA-1 证书
平台推荐方式
  • 生成密钥

# -keystore 填写密钥的path
# -alias 填写别名
# -keyalg RSA 加密方式
# -keysize 2048 密钥字节长度 
# -validity 10000 有效期(天)
keytool -genkey -v -keystore my-release-key.keystore -alias firebase -keyalg RSA -keysize 2048 -validity 10000
  • 接下来会提示输入一系列的信息

What is your first and last name?:输入你的名字和姓氏。
What is the name of your organizational unit?:输入你的组织单位名称。
What is the name of your organization?:输入你的组织名称。
What is the name of your City or Locality?:输入你的城市或区 域名称。
What is the name of your State or Province?:输入你的省/市/自治区名称。
What is the two-letter country code for this unit?:输入你的国家/地区代号(两个字母),比如中国为CN。
  • 最后提示输入密码,这个密码需要记忆,之后再次验证需要使用。

  • 生成之后会将证书签名的内容打印到控制台

密钥库类型: PKCS12
密钥库提供方: SUN

您的密钥库包含 1 个条目

别名: alias_name
创建日期: 2025年3月14日
条目类型: PrivateKeyEntry
证书链长度: 1
证书[1]:
所有者: CN=unknow, OU=unknow, O=unknow, L=unknow, ST=unknow, C=unknow
发布者: CN=unknow, OU=unknow, O=unknow, L=unknow, ST=unknow, C=unknow
序列号: 4f91b5521a1c189a
生效时间: Fri Mar 14 15:16:39 CST 2025, 失效时间: Tue Jul 30 15:16:39 CST 2052
证书指纹:
         SHA1: 55:5C:CD:79:BA:5D:30:DD:CD:4C:2E:DD:F7:32:DA:E0:FD:C1:D9:8E
         SHA256: 48:2B:E1:53:83:1C:02:CF:69:D6:59:2A:BD:EA:4F:5A:93:7C:57:30:B7:F8:05:1A:35:4E:2C:7F:E8:ED:39:95
签名算法名称: SHA384withRSA
主体公共密钥算法: 2048 位 RSA 密钥
版本: 3

扩展: 

#1: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: DD 2A 32 5A 3C E4 BA 60   4C F0 16 87 61 4A 1A 3D  .*2Z<..`L...aJ.=
0010: B2 80 CE 61                                        ...a
]
]



*******************************************
*******************************************
  • 生成完成之后可以通过下面命令验证密钥有效性,输入密码之后如果正常输出了密钥内容则是有效的。

keytool -list -v -keystore my-release-key.keystore
  • 在module的build.gradle 中配置密钥

signingConfigs {
  myConfig {
    def hib_key = rootProject.file('hib-applications/hib-app/hib_key')
    storeFile hib_key
    storePassword 'xxxxx'
    keyAlias 'xxx'
    keyPassword 'xxxxx'
    v2SigningEnabled true
    v1SigningEnabled true
  }
}

buildTypes {
  release {
    signingConfig signingConfigs.myConfig
  }

  debug {
    signingConfig signingConfigs.myConfig
  } 
}
  • 将打印内容中的 SHA-1 复制到表格中,如果已经在google商店中进行注册,可以继续验证应用所有权,否则直接点击下方的创建即可

验证方式

        可以通过 ./gradlew signingreport 来验证上面的命令生成的证书是否是项目设定的证书,

./gradlew signingreport

> Task :app:signingReport
Variant: debug
Config: debug
Store: /Users/liuqianzhen/.android/debug.keystore
Alias: AndroidDebugKey
MD5: 93:0B:51:CE:C9:9F:BB:D0:69:85:B6:01:40:51:F0:19
SHA1: 4C:D4:50:85:BF:57:94:F4:6A:9C:1A:8C:A4:94:BE:0A:3C:88:7B:F2
SHA-256: F3:EE:09:A6:D5:BF:D5:73:13:4E:A1:63:B3:EB:BE:9E:2E:8F:E2:93:03:D3:06:D7:60:04:B4:75:27:02:CC:8B
Valid until: 2054年8月13日星期四
----------
Variant: release
Config: null
Store: null
Alias: null
----------
Variant: debugAndroidTest
Config: debug
Store: /Users/liuqianzhen/.android/debug.keystore
Alias: AndroidDebugKey
MD5: 93:0B:51:CE:C9:9F:BB:D0:69:85:B6:01:40:51:F0:19
SHA1: 4C:D4:50:85:BF:57:94:F4:6A:9C:1A:8C:A4:94:BE:0A:3C:88:7B:F2
SHA-256: F3:EE:09:A6:D5:BF:D5:73:13:4E:A1:63:B3:EB:BE:9E:2E:8F:E2:93:03:D3:06:D7:60:04:B4:75:27:02:CC:8B
Valid until: 2054年8月13日星期四
----------

BUILD SUCCESSFUL in 1s
1 actionable task: 1 executed

2、工程中使用

配置依赖
//  google 三方登录依赖
implementation "androidx.credentials:credentials:1.2.0"
implementation "androidx.credentials:credentials-play-services-auth:1.2.0"
implementation "com.google.android.libraries.identity.googleid:googleid:1.0.0"
implementation 'com.google.android.gms:play-services-auth:20.4.0'

implementation 'com.google.api-client:google-api-client:1.32.1'
implementation 'com.google.oauth-client:google-oauth-client:1.32.1'
implementation 'com.google.http-client:google-http-client-gson:1.32.1'
implementation 'com.google.http-client:google-http-client-android:1.32.1'
接口调用
  • google三方登录

private suspend fun signIn(context: Context) {
  Log.i(TAG, "signIn")

  val credentialManager = CredentialManager.create(context)

  val nonce = generateNonce()
  Log.i(TAG, "signIn nonce: $nonce")

  //  这里考虑先试用 isFilterByAuthorizedAccounts = false 可以提供创建新用户的能力
  val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
    .setFilterByAuthorizedAccounts(false)
    .setServerClientId(CLIENT_ID)
    .setAutoSelectEnabled(true)
    .setNonce(nonce)
    .build()

  val request: GetCredentialRequest = GetCredentialRequest.Builder()
    .addCredentialOption(googleIdOption)
    .build()

  Log.i(TAG, "signIn connection success")
  coroutineScope {
    launch {
      try {
        val result = credentialManager.getCredential(
          context, request,
        )
        Log.i(TAG, "signIn getCredential success")
        withContext(Dispatchers.IO) {
          handleSignIn(result)
        }
      } catch (e: Exception) {
        handleFailure(e)
      }
    }
  }
}

private fun handleSignIn(result: GetCredentialResponse) {
  Log.i(TAG, "handleSignIn")
  // Handle the successfully returned credential.
  val credential = result.credential

  // 初始化 NetHttpTransport 和 JacksonFactory
  val transport = NetHttpTransport()
  val jsonFactory = AndroidJsonFactory.getDefaultInstance()

  var responseJson: String? = null

  when (credential) {

    // Passkey credential
    is PublicKeyCredential -> {
      // Share responseJson such as a GetCredentialResponse on your server to
      // validate and authenticate
      responseJson = credential.authenticationResponseJson
    }

    // Password credential
    is PasswordCredential -> {
      // Send ID and password to your server to validate and authenticate.
      val username = credential.id
      val password = credential.password
    }

    // GoogleIdToken credential
    is CustomCredential -> {
      Log.i(TAG, "handleSignIn credential type: ${credential.type}")
      if (credential.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) {
        try {
          // Use googleIdTokenCredential and extract the ID to validate and authenticate on your server.
          val googleIdTokenCredential = GoogleIdTokenCredential.createFrom(credential.data)

          val idTokenString = googleIdTokenCredential.idToken

          // You can use the members of googleIdTokenCredential directly for UX purposes,
          // but don't use them to store or control access to user data.
          // For that you first need to validate the token: pass googleIdTokenCredential.getIdToken() to the backend server.
          var verifier = GoogleIdTokenVerifier.Builder(transport, jsonFactory)
            .setAudience(Collections.singletonList(CLIENT_ID))
            .build()
          var idToken = verifier.verify(idTokenString)
          // To get a stable account identifier (e.g. for storing user data), use the subject ID:
          idToken.getPayload().getSubject()
          Log.i(TAG, "Received a valid google id token response: " + idToken)
        } catch (e: GoogleIdTokenParsingException) {
          Log.e(TAG, "Received an invalid google id token response1", e)
        } catch (e: Exception) {
          Log.e(TAG, "Received an invalid google id token response2", e)
        }
      } else {
        // Catch any unrecognized custom credential type here.
        Log.e(TAG, "Unexpected type of credential")
      }
    }

    else -> {
      // Catch any unrecognized credential type here.
      Log.e(TAG, "Unexpected type of credential")
    }
  }
}

        setFilterByAuthorizedAccounts 首先通过调用参数设置为 true 的API,检查用户是否有任何曾经用于登录应用的帐户。用户可以选择可用的帐户进行登录。如果没有可用的授权Google 帐户,需要再次调用 API 并设置setFilterByAuthorizedAccounts 为 false,来提示用户使用其任何可用帐户进行注册。

        使用 google 注册之后,用户可以通过设置setAutoSelectEnabled 为 true 选择自动登录

  • 设备无google账号,调起登录页面

fun handleFailure(exception: Exception) {
  Log.e(TAG, "handleFailure", exception)
  //	如果是因为设备上没有登录账号,则触发登录google账户流程
  if (exception is NoCredentialException) {
    val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
      .requestEmail()
      .build()

    val googleSignInClient: GoogleSignInClient = GoogleSignIn.getClient(this, gso)

    var signInIntent: Intent = googleSignInClient.getSignInIntent()
    this.startActivityForResult(signInIntent, RC_SIGN_IN)
  }
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
  super.onActivityResult(requestCode, resultCode, data)
  if (requestCode == RC_SIGN_IN) {
    val task: Task<GoogleSignInAccount> = GoogleSignIn.getSignedInAccountFromIntent(data)
    handleSignInResult(task)
  }
}

private fun handleSignInResult(completedTask: Task<GoogleSignInAccount>) {
  Log.i(TAG, "handleSignInResult: login success ")
  // 初始化 NetHttpTransport 和 JacksonFactory
  val transport = NetHttpTransport()
  val jsonFactory = AndroidJsonFactory.getDefaultInstance()

  try {
    val account: GoogleSignInAccount = completedTask.getResult(ApiException::class.java)
    // 此处可以处理登录成功后的逻辑,使用 account 获取所需的凭据
    account?.let{
      var idTokenString = it.getIdToken();
      var authCode = it.getServerAuthCode();
      Log.i(TAG, "Received an id token: " + idTokenString)

      // 根据您的需求使用 idToken 或 authCode
      // 例如,传递 idToken 到您的后台服务器进行验证
      var verifier = GoogleIdTokenVerifier.Builder(transport, jsonFactory)
        .setAudience(Collections.singletonList(CLIENT_ID))
        .build()
      var idToken = verifier.verify(idTokenString)
      // To get a stable account identifier (e.g. for storing user data), use the subject ID:
      idToken.getPayload().getSubject()
      Log.i(TAG, "Received an verifire id token: " + idToken)
    }
  } catch (e: ApiException) {
    Log.w(TAG, "signInResult:failed code=" + e.getStatusCode())
    // 处理登录失败的逻辑
  }
}
  • 退出登录

        使用 google 登录之后如果退出登录建议清空会话,便于下次进行登录

fun exit(context: Context) {
  Log.i(TAG, "exit")
  val credentialManager = CredentialManager.create(context)

  lifecycleScope.launch {
    val clearCredentialStateRequest = ClearCredentialStateRequest()
    credentialManager.clearCredentialState(clearCredentialStateRequest)
  }
}

        关于请求回来的 googleIdTokenCredential,将其发送到服务端进行验证,再对返回的结果进行处理,https://developers.google.com/identity/gsi/web/guides/verify-google-id-token?hl=zh-cn

3、遇到的问题

问题1:

androidx.credentials.exceptions.NoCredentialException: During begin sign in, failure response from one tap: 16: [28433] Cannot find a matching credential.

原因:设备上未登录google play,登录之后即可解决,如果是这个exception,可以弹一个toast进行提示,或者弹出google登录导引

问题2:

androidx.credentials.exceptions.NoCredentialException: During begin sign in, failure response from one tap: 10: [28444] Developer console is not set up correctly.

原因:google平台上创建 OAuth 2.0 客户端ID 需要使用工程生成的 SHA-1,平台上建议使用 keytool 生成 SHA-1,但是这样生成出来的无法使用,使用 ./gradlew signingreport 生成的 SHA-1,可以配置完成

问题3:

androidx.credentials.exceptions.GetCredentialCancellationException: activity is cancelled by the user.

原因:在 google 平台上创建了 Android OAuth,但是并不能直接使用,需要再次创建一个 Web OAuth,使用 Web ID 进行登录,才可以顺利的调起 google 验证账户登录页面

问题4:

android.os.NetworkOnMainThreadException

原因:网络请求的部分不能在主线程上执行,网络请求一定要放在IO线程上

完整代码:

https://github.com/BugFreeCat/thirdLogin

欢迎各位大佬观赏、评论、留个赞

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐