You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

特定账号设备下GoogleAuth验证IDToken报错[No pem found]求助

特定设备+账号组合下Google SignIn idToken验证失败问题

场景说明

  • Android应用(Kotlin+Jetpack Compose):通过GoogleSignIn获取账号idToken,发送至Node.js服务器验证
  • Node.js服务器:调用GoogleAuth.OAuth2Client.verifyIdToken()验证idToken

仅Device A(三星GS8,Android 9)+Account A+发布包+生产服务器组合出现失败,其余场景均正常:

  • 虚拟机调试客户端→开发服务器:正常
  • 虚拟机调试客户端→生产服务器:正常
  • 虚拟机发布客户端→生产服务器:正常
  • 物理机调试客户端→生产服务器(Account A+Device A):正常
  • 物理机发布客户端→生产服务器:其他组合均正常

异常信息

Error: No pem found for envelope: {"alg":"RS256","kid":"4qa48u3nvj3498fru03984nmr20938yf7c63b4f8","typ":"JWT"}
at OAuth2Client.verifySignedJwtWithCertsAsync (.../node_modules/google-auth-library/build/src/auth/oauth2client.js:608:19)
at OAuth2Client.verifyIdTokenAsync (.../node_modules/google-auth-library/build/src/auth/oauth2client.js:444:34)
at processTicksAndRejections (internal/process/task_queues.js:93:5)
at async Object.VerifyIdToken (.../server_auth_file.js:666:18)

相关代码

客户端代码(仅收到500错误,无本地崩溃)

import ...

class LoginActivity : ComponentActivity() {
    private lateinit var signinActivity: ActivityResultLauncher<Intent>

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        signinActivity = registerForActivityResult(ActivityResultContracts.StartActivityForResult())
        { result: ActivityResult ->
            try{
                val task = GoogleSignIn.getSignedInAccountFromIntent(result.data)
                handleSignInResult(task)
            } catch (err:ApiException){
                // todo -- check error code against GoogleSignInStatusCodes
                val intent = Intent()
                intent.putExtra("success",false)
                intent.putExtra("message","cancelled")
                setResult(RESULT_CANCELED, intent)
                finish()
            }
        }
        signIn()
    }
    
    private fun getGoogleSignInClient(): GoogleSignInClient {
    val gso =
        GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .requestIdToken((this as Context).resources.getString(R.string.APP_CLIENT_ID))
            .requestEmail()
            .build()
        return GoogleSignIn.getClient(this, gso)
    }

    private fun signIn() {
        val signInIntent = getGoogleSignInClient().signInIntent
        signinActivity.launch(signInIntent)
    }

    private fun handleSignInResult(completedTask: Task<GoogleSignInAccount>) {
            val accountData = completedTask.getResult(
                ApiException::class.java
            )
            // refresh server access token
            val accessToken = refreshAccessToken(applicationContext,accountData.idToken) 
            val intent = Intent()
            intent.putExtra("accessToken",accessToken as Serializable)
            setResult(RESULT_OK, intent)
            finish()
    }
    
    private fun refreshAccessToken(context:Context,idToken: String): AccessToken {
        // ==========================================
        // ==========================================
        // VVVVVV Breaks on this rest call VVVVVVVV

        val response = restCallToServer("get/accesstoken", "authtoken=${idToken}")

        // ^^^^^^ Breaks on this rest call ^^^^^^^^
        // ==========================================
        // ==========================================
        
        // creates a serializable token used by rest of app from data provided by server
        val accessToken = AccessToken(response!!.getJSONObject("accessToken")) 
        return accessToken;
    }

服务器代码(抛出上述异常)

const GOOGLE_OAUTH_CLIENT = new GoogleAuth.OAuth2Client(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET);

async function VerifyIdToken(idToken,appName){
    let credential,googuid;
    
    // vvvvv exception here vvvvv
    // vvvvv exception here vvvvv
    // vvvvv exception here vvvvv
    const ticket = await GOOGLE_OAUTH_CLIENT.verifyIdToken({
        idToken,
        audience:GOOGLE_CLIENT_ID
    });
    // ^^^^^ exception here ^^^^^
    // ^^^^^ exception here ^^^^^
    // ^^^^^ exception here ^^^^^
    
    const credential = ticket.getPayload();
    const googuid = credential.sub;
    const email = credential.email;
    // ... 
    // access_token generated and tied to user account
    // ...
    // access_token sent back to client
}

已尝试操作

  • 无法测试物理设备连接开发服务器
  • Device A上其他账号正常,Account A在其他设备上正常
  • 物理机、虚拟机、调试包、发布包(除异常组合外)均正常
  • Google自动化发布测试无问题
  • 新版本/AAB无法解决问题

补充说明

  • 该问题为新出现异常,触发原因未知
  • 同一版本的其他渠道包无此问题
  • 怀疑设备缓存证书或kid生成逻辑导致,但不确定

排查方向建议

  1. 捕获异常idToken并解析:在服务器端临时添加日志,捕获Account A在Device A发布包下生成的idToken,手动解析其header部分(对应异常中的kid),确认该kid是否存在于Google的公钥列表中。
  2. 强制刷新Google公钥缓存:google-auth-library会缓存公钥,可能服务器缓存的列表未包含该kid。可在初始化OAuth2Client时自定义缓存实现,或在异常时主动调用GOOGLE_OAUTH_CLIENT.getIapPublicKeys()刷新缓存后重试验证。
  3. 检查设备端Google Play服务:Device A的Google Play服务版本过低或异常,可能导致生成的idToken使用了服务器未同步的密钥。引导用户更新或清除Google Play服务缓存后重试。
  4. 发布包签名校验:确认异常渠道的发布包签名与正常渠道是否一致,Google SignIn对签名校验严格,签名差异可能导致idToken异常。
  5. 账号特殊属性排查:检查Account A是否存在特殊设置(如双重验证、账号异常状态),结合Device A环境可能触发特殊逻辑。

内容的提问来源于stack exchange,提问作者cedar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 01:47:18