Google 授权登录 V2 接入文档 王者归来

Google 授权登录 V2 接入文档

版本 : V2.0
更新日期 : 2026-05-27
适用平台 : Android (API 23+)
技术方案: Credential Manager + Sign-In with Google


目录

  1. 概述
  2. Gradle 依赖配置
  3. 完整源码
  4. 核心类架构
  5. 接入步骤详解
  6. 关键配置项
  7. 登录方式对比
  8. 常见问题
  9. 注意事项
  10. 参考链接

一、概述

本文档介绍 Android 项目中使用 Credential Manager 接入 Google 授权登录(Sign-In with Google)的 V2 版本实现。该方案基于 androidx.credentials 库,替代了旧版的 Google Sign-In SDK。

技术演进

版本 方案 状态
V1 (旧版) com.google.android.gms:play-services-auth 已废弃
V2 (新版) androidx.credentials + Credential Manager 推荐使用

官方文档


二、Gradle 依赖配置

app/build.gradle 中添加以下依赖:

arduino 复制代码
dependencies {
    // ═══════════════════════════════════════════
    // Credential Manager 核心库
    // ═══════════════════════════════════════════
    implementation "androidx.credentials:credentials:1.2.2"
    implementation "androidx.credentials:credentials-play-services-auth:1.2.2"
    
    // ═══════════════════════════════════════════
    // Google ID 令牌支持
    // ═══════════════════════════════════════════
    implementation "com.google.android.libraries.identity.googleid:googleid:1.1.1"
}

⚠️ 版本兼容警告
androidx.credentials 1.7.0-alpha02+ 要求 Android Gradle Plugin 8.6.0+

若项目使用 AGP 8.0.x,请使用 1.2.2 稳定版


三、完整源码

TestGoogleSdk.java

java 复制代码
package com.example.myapplication;

import android.app.Activity;
import android.os.CancellationSignal;
import android.text.TextUtils;
import android.util.ArrayMap;
import androidx.annotation.NonNull;
import androidx.credentials.ClearCredentialStateRequest;
import androidx.credentials.Credential;
import androidx.credentials.CredentialManager;
import androidx.credentials.CredentialManagerCallback;
import androidx.credentials.CustomCredential;
import androidx.credentials.GetCredentialRequest;
import androidx.credentials.GetCredentialResponse;
import androidx.credentials.exceptions.ClearCredentialException;
import androidx.credentials.exceptions.GetCredentialCancellationException;
import androidx.credentials.exceptions.GetCredentialException;
import com.google.android.libraries.identity.googleid.GetSignInWithGoogleOption;
import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential;
import org.json.JSONObject;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.Executors;

/**
 * Google 授权登录 V2 实现类
 * 
 * 基于 Credential Manager 的 Sign-In with Google 方案
 * 替代旧版 Google Sign-In SDK
 * 
 * 官方文档:
 * - 新版: https://developer.android.com/identity/sign-in/credential-manager-siwg?hl=zh-cn
 * - 旧版: https://developer.android.google.cn/identity/legacy/gsi?hl=zh-cn
 * 
 * 参考博客:
 * - https://blog.csdn.net/zll18201518375/article/details/138577963
 */
public class TestGoogleSdk {

    // ═══════════════════════════════════════════
    // 成员变量
    // ═══════════════════════════════════════════
    
    /** 凭证请求对象 */
    GetCredentialRequest getCredentialRequest;
    
    /** CredentialManager 实例 */
    CredentialManager credentialManager;
    
    /** 取消信号 */
    CancellationSignal cancellationSignal;
    
    /** 日志标签 */
    private static final String TAG = "TestGoogleSdk";
    
    /** Google OAuth Web 客户端 ID */
    private String clientId = "162951404116-ebkc28baqbt288h3diklgfng7i00l34j.apps.googleusercontent.com";
    
    /** 单例实例 */
    private static TestGoogleSdk instance = null;

    // ═══════════════════════════════════════════
    // 单例模式
    // ═══════════════════════════════════════════
    
    /**
     * 私有构造方法
     */
    private TestGoogleSdk() {
        // 防止外部实例化
    }

    /**
     * 获取单例实例(线程安全 - 双重检查锁定)
     * 
     * @return TestGoogleSdk 单例对象
     */
    public static TestGoogleSdk getInstance() {
        if (instance == null) {
            synchronized (TestGoogleSdk.class) {
                if (instance == null) {
                    instance = new TestGoogleSdk();
                }
            }
        }
        return instance;
    }

    // ═══════════════════════════════════════════
    // 初始化
    // ═══════════════════════════════════════════
    
    /**
     * 初始化 CredentialManager
     * 
     * 应在 Activity 的 onCreate 中调用
     * 
     * @param activity 当前 Activity 上下文
     */
    public void init(Activity activity) {
        try {
            credentialManager = CredentialManager.create(activity);
        } catch (Exception e) {
            LogUtil.e(TAG, "get client id fail," + e.getMessage());
        }
    }

    // ═══════════════════════════════════════════
    // 登录
    // ═══════════════════════════════════════════
    
    /**
     * 发起 Google 登录请求
     * 
     * 调用流程:
     * 1. 构建 GetSignInWithGoogleOption(配置 clientId 和 nonce)
     * 2. 构建 GetCredentialRequest
     * 3. 调用 CredentialManager.getCredentialAsync() 异步获取凭证
     * 4. 在回调中处理登录结果
     * 
     * @param activity 当前 Activity 上下文
     */
    public void login(Activity activity) {
        LogUtil.d(TAG, "google_server_client_id:" + clientId);
        
        // ───────────────────────────────────────
        // 方案一: GetGoogleIdOption(底部动作条界面)
        // 特点: 已登录 Play 商店的用户体验更流畅
        // 缺点: 未登录 Play 商店时不会拉起商店
        // ───────────────────────────────────────
        /*
        GetGoogleIdOption getGoogleIdOption = new GetGoogleIdOption.Builder()
                .setFilterByAuthorizedAccounts(false)
                .setServerClientId(clientId)  // webID
                .setAutoSelectEnabled(true)
                .build();
        */
        
        // ───────────────────────────────────────
        // 方案二: GetSignInWithGoogleOption(普通登录界面)
        // 特点: 标准 Google 登录弹窗,兼容性更好
        // 推荐: 通用场景,未登录 Play 商店也能使用
        // ───────────────────────────────────────
        String nonce = "11121";
        // 生产环境建议: EncodeUtil.encodeBase64(UUID.randomUUID().toString());
        
        GetSignInWithGoogleOption signInWithGoogleOption = 
            new GetSignInWithGoogleOption.Builder(clientId)
                .setNonce(nonce)
                .build();

        // 构建凭证请求
        getCredentialRequest = new GetCredentialRequest.Builder()
                // .addCredentialOption(getGoogleIdOption)  // 底部动作条界面
                .addCredentialOption(signInWithGoogleOption)  // 普通登录界面
                .build();

        // 设置取消信号
        cancellationSignal = new CancellationSignal();
        cancellationSignal.setOnCancelListener(() -> {
            // 用户取消操作时的回调
        });

        // 异步获取凭证
        credentialManager.getCredentialAsync(
            activity, 
            getCredentialRequest, 
            cancellationSignal, 
            Executors.newSingleThreadExecutor(), 
            new CredentialManagerCallback<GetCredentialResponse, GetCredentialException>() {
                
                @Override
                public void onResult(GetCredentialResponse credentialResponse) {
                    LogUtil.e(TAG, "onResult");
                    handleResult(credentialResponse, nonce);
                }

                @Override
                public void onError(@NonNull GetCredentialException e) {
                    LogUtil.e(TAG, "onError:" + e);
                    if (e instanceof GetCredentialCancellationException) {
                        LogUtil.e(TAG, "login cancel " + e.getMessage());
                    } else {
                        // 其他错误处理
                    }
                }
            }
        );
    }

    // ═══════════════════════════════════════════
    // 登录结果处理
    // ═══════════════════════════════════════════
    
    /**
     * 处理登录返回的凭证数据
     * 
     * 解析流程:
     * 1. 获取 Credential 对象
     * 2. 判断是否为 CustomCredential 类型
     * 3. 判断凭证类型是否为 GOOGLE_ID_TOKEN_CREDENTIAL
     * 4. 提取 idToken、用户信息
     * 5. 构造登录数据传给后端
     * 
     * @param credentialResponse 登录响应对象
     * @param nonce 登录时使用的 nonce(用于安全校验)
     */
    private void handleResult(GetCredentialResponse credentialResponse, String nonce) {
        Credential credential = credentialResponse.getCredential();
        
        if (credential instanceof CustomCredential) {
            // 凭证类型: com.google.android.libraries.identity.googleid.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL
            if (GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL.equals(credential.getType())) {
                GoogleIdTokenCredential googleIdTokenCredential = null;
                String err = "";
                
                try {
                    googleIdTokenCredential = GoogleIdTokenCredential.createFrom(credential.getData());
                } catch (Exception e) {
                    err = e.getMessage();
                    e.printStackTrace();
                }
                
                if (googleIdTokenCredential == null) {
                    return;
                }
                
                // ───────────────────────────────────────
                // 提取用户信息
                // ───────────────────────────────────────
                String idToken = googleIdTokenCredential.getIdToken();
                String id = googleIdTokenCredential.getId();
                // String accountName = googleIdTokenCredential.getEmail();
                String name = googleIdTokenCredential.getGivenName();
                String familyName = googleIdTokenCredential.getFamilyName();
                
                LogUtil.d(TAG, "google login success, idToken:" + idToken);
                LogUtil.d(TAG, "google login success, id:" + id + ", name:" + name + ", familyName:" + familyName);
                
                // ───────────────────────────────────────
                // 构造登录数据
                // ───────────────────────────────────────
                Map<String, Object> googleloginInfo = new ArrayMap<>();
                googleloginInfo.put("google", Collections.singletonMap("token", idToken));
                
                // TODO: 将 idToken 传给后端验证
                // authStateListener.onAuthSuccess(loginType(), googleloginInfo);
                
            } else {
                String err = "Unexpected type of credential" + credential.getType();
                LogUtil.e(TAG, err);
            }
        } else {
            String err = "Unexpected type of credential, className:" + credential.getClass().getName();
            LogUtil.e(TAG, err);
        }
    }

    // ═══════════════════════════════════════════
    // 登出
    // ═══════════════════════════════════════════
    
    /**
     * 清除 Google 登录凭证状态
     * 
     * 调用 CredentialManager.clearCredentialStateAsync() 清除本地凭证
     * 
     * @param activity 当前 Activity 上下文
     */
    public void logout(Activity activity) {
        LogUtil.d(TAG, "do logout");
        
        ClearCredentialStateRequest clearCredentialStateRequest = new ClearCredentialStateRequest();
        CancellationSignal cancellationSignal = new CancellationSignal();
        cancellationSignal.setOnCancelListener(() -> {
            LogUtil.e(TAG, "logout onCancel");
        });

        if (credentialManager != null) {
            credentialManager.clearCredentialStateAsync(
                clearCredentialStateRequest,
                cancellationSignal,
                Executors.newSingleThreadExecutor(),
                new CredentialManagerCallback<Void, ClearCredentialException>() {

                    @Override
                    public void onResult(Void result) {
                        LogUtil.d(TAG, "Google logout onSuccess");
                    }

                    @Override
                    public void onError(ClearCredentialException e) {
                        LogUtil.e(TAG, "logout onError:" + e);
                    }
                }
            );
        }
    }

    // ═══════════════════════════════════════════
    // 工具方法
    // ═══════════════════════════════════════════
    
    /**
     * 获取登录类型标识
     * 
     * @return 登录类型常量(GOOGLE)
     */
    public int loginType() {
        return TestLoginType.GOOGLE;
    }
}

四、核心类架构

4.1 类图

css 复制代码
┌─────────────────────────────────────┐
│          TestGoogleSdk                │
│  ┌─────────────────────────────┐    │
│  │  - instance: TestGoogleSdk    │    │
│  │  - credentialManager        │    │
│  │  - getCredentialRequest     │    │
│  │  - cancellationSignal       │    │
│  │  - clientId: String         │    │
│  └─────────────────────────────┘    │
│  ┌─────────────────────────────┐    │
│  │  + getInstance()            │    │
│  │  + init(Activity)           │    │
│  │  + login(Activity)          │    │
│  │  + logout(Activity)         │    │
│  │  + loginType(): int         │    │
│  │  - handleResult(...)        │    │
│  └─────────────────────────────┘    │
└─────────────────────────────────────┘
           │
           ▼
┌─────────────────────────────────────┐
│      CredentialManager              │
│  (androidx.credentials)             │
│                                     │
│  + create(context)                  │
│  + getCredentialAsync(...)          │
│  + clearCredentialStateAsync(...)   │
└─────────────────────────────────────┘

4.2 方法说明

方法 访问权限 说明
getInstance() public static 获取单例实例(线程安全 - 双重检查锁定)
init(Activity) public 初始化 CredentialManager
login(Activity) public 发起 Google 登录请求
logout(Activity) public 清除登录凭证状态
loginType() public 返回登录类型标识(GOOGLE)
handleResult(...) private 处理登录返回的凭证数据

五、接入步骤详解

5.1 初始化

在 Activity 的 onCreate 中调用:

scss 复制代码
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    
    // 初始化 Google SDK
    TestGoogleSdk.getInstance().init(this);
}

内部实现详解

typescript 复制代码
public void init(Activity activity) {
    try {
        // 创建 CredentialManager 实例
        // 这是 Credential Manager 库的入口类
        credentialManager = CredentialManager.create(activity);
    } catch (Exception e) {
        LogUtil.e(TAG, "get client id fail," + e.getMessage());
    }
}

5.2 发起登录

kotlin 复制代码
// 点击登录按钮时调用
TestGoogleSdk.getInstance().login(this);
登录流程详解:

Step 1:配置 GetSignInWithGoogleOption

ini 复制代码
// nonce 用于防止重放攻击,生产环境应动态生成
String nonce = "11121";

// 构建 Sign-In with Google 选项
GetSignInWithGoogleOption signInWithGoogleOption = 
    new GetSignInWithGoogleOption.Builder(clientId)
        .setNonce(nonce)
        .build();

参数说明

  • clientId: Google Cloud Console 中创建的 Web 应用客户端 ID(格式:xxx.apps.googleusercontent.com
  • nonce: 随机字符串,用于安全校验,防止重放攻击

Step 2:构建 GetCredentialRequest

ini 复制代码
getCredentialRequest = new GetCredentialRequest.Builder()
    .addCredentialOption(signInWithGoogleOption)
    .build();

Step 3:异步获取凭证

less 复制代码
// 创建取消信号(用于用户主动取消操作)
cancellationSignal = new CancellationSignal();
cancellationSignal.setOnCancelListener(() -> {
    // 取消回调
});

// 异步调用 CredentialManager 获取凭证
credentialManager.getCredentialAsync(
    activity,                    // 当前 Activity
    getCredentialRequest,        // 凭证请求
    cancellationSignal,          // 取消信号
    Executors.newSingleThreadExecutor(),  // 后台线程执行器
    new CredentialManagerCallback<GetCredentialResponse, GetCredentialException>() {
        
        @Override
        public void onResult(GetCredentialResponse credentialResponse) {
            // 登录成功,处理凭证
            handleResult(credentialResponse, nonce);
        }

        @Override
        public void onError(@NonNull GetCredentialException e) {
            // 登录失败或取消
            if (e instanceof GetCredentialCancellationException) {
                // 用户取消登录
            } else {
                // 其他错误(网络异常、配置错误等)
            }
        }
    }
);

5.3 处理登录结果

ini 复制代码
private void handleResult(GetCredentialResponse credentialResponse, String nonce) {
    Credential credential = credentialResponse.getCredential();
    
    if (credential instanceof CustomCredential) {
        // 检查凭证类型是否为 Google ID Token
        if (GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL.equals(credential.getType())) {
            
            GoogleIdTokenCredential googleIdTokenCredential = 
                GoogleIdTokenCredential.createFrom(credential.getData());
            
            // 提取关键信息
            String idToken = googleIdTokenCredential.getIdToken();      // 用于后端验证的令牌
            String id = googleIdTokenCredential.getId();                 // 用户唯一标识
            String name = googleIdTokenCredential.getGivenName();        // 名字
            String familyName = googleIdTokenCredential.getFamilyName(); // 姓氏
            // String email = googleIdTokenCredential.getEmail();        // 邮箱
            
            // 构造登录数据传给后端
            Map<String, Object> googleloginInfo = new ArrayMap<>();
            googleloginInfo.put("google", Collections.singletonMap("token", idToken));
            
            // TODO: 调用后端接口验证 idToken
        }
    }
}

5.4 登出

kotlin 复制代码
// 点击登出按钮时调用
TestGoogleSdk.getInstance().logout(this);

内部实现详解

typescript 复制代码
public void logout(Activity activity) {
    // 创建清除凭证请求
    ClearCredentialStateRequest clearCredentialStateRequest = new ClearCredentialStateRequest();
    
    // 创建取消信号
    CancellationSignal cancellationSignal = new CancellationSignal();
    cancellationSignal.setOnCancelListener(() -> {
        LogUtil.e(TAG, "logout onCancel");
    });

    // 异步清除凭证状态
    if (credentialManager != null) {
        credentialManager.clearCredentialStateAsync(
            clearCredentialStateRequest,
            cancellationSignal,
            Executors.newSingleThreadExecutor(),
            new CredentialManagerCallback<Void, ClearCredentialException>() {
                
                @Override
                public void onResult(Void result) {
                    LogUtil.d(TAG, "Google logout onSuccess");
                    // 清除成功,更新 UI 状态
                }

                @Override
                public void onError(ClearCredentialException e) {
                    LogUtil.e(TAG, "logout onError:" + e);
                    // 清除失败处理
                }
            }
        );
    }
}

六、关键配置项

6.1 clientId 配置

ini 复制代码
private String clientId = "162951404116-ebkc28baqbt288h3diklgfng7i00l34j.apps.googleusercontent.com";

获取方式

  1. 登录 Google Cloud Console
  2. 选择项目 → APIs & ServicesCredentials
  3. 点击 Create CredentialsOAuth 2.0 Client ID
  4. 选择 Web application 类型
  5. 配置 Authorized redirect URIs(如需要)
  6. 复制 Client ID
arduino 复制代码
Google Cloud Console
├── APIs & Services
│   ├── Credentials
│   │   ├── Create Credentials
│   │   │   └── OAuth 2.0 Client ID
│   │   │       └── Application type: Web application
│   │   └── [Your Client ID]

6.2 Nonce 配置

当前代码使用固定 nonce,生产环境强烈建议改为动态生成

ini 复制代码
// ❌ 开发环境(不安全)
String nonce = "11121";

// ✅ 生产环境(推荐)
String nonce = Base64.encodeToString(
    UUID.randomUUID().toString().getBytes(), 
    Base64.NO_WRAP
);

Nonce 作用

  • 防止重放攻击
  • 确保 idToken 的唯一性
  • 服务端可验证 nonce 是否匹配

七、登录方式对比

特性 GetSignInWithGoogleOption GetGoogleIdOption
类名 GetSignInWithGoogleOption GetGoogleIdOption
界面 标准 Google 登录弹窗 底部 Bottom Sheet 动作条
Play 商店依赖 不依赖 依赖(需已登录)
用户体验 传统登录流程 更流畅(一键登录)
适用场景 通用场景、未登录 Play 商店 已登录 Play 商店的用户
兼容性
本项目使用

选择建议 :本项目使用 GetSignInWithGoogleOption,因为底部动作条在未登录 Play 商店时不会拉起商店,兼容性更好。


八、常见问题

Q1: 登录时提示 "No credentials available"

原因:设备上没有可用的 Google 账号

解决

  1. 确保设备已添加 Google 账号(设置 → 账号 → 添加账号)
  2. 检查网络连接
  3. 确认 clientId 配置正确

Q2: idToken 验证失败

原因

  1. nonce 不匹配
  2. idToken 已过期
  3. clientId 与 Google Cloud Console 配置不一致

解决

  1. 确保前后端使用相同的 nonce
  2. 及时使用 idToken(有效期约 1 小时)
  3. 核对 clientId 配置

Q3: AGP 版本不兼容

错误信息

arduino 复制代码
Dependency 'androidx.credentials:credentials:1.7.0-alpha02' 
requires Android Gradle plugin 8.6.0 or higher.

解决:降级到稳定版

arduino 复制代码
implementation "androidx.credentials:credentials:1.2.2"
implementation "androidx.credentials:credentials-play-services-auth:1.2.2"

九、注意事项

  1. AGP 版本兼容
    androidx.credentials 1.2.2 兼容 AGP 8.0.x,如需使用更高版本需同步升级 AGP 和 Gradle。

  2. 线程处理

    登录回调在后台线程执行,更新 UI 需切换到主线程:

    less 复制代码
    activity.runOnUiThread(() -> {
        // 更新 UI
    });
  3. Nonce 安全

    生产环境务必使用随机生成的 nonce,防止重放攻击。

  4. idToken 验证

    获取到的 idToken 必须传给服务端,通过 Google 公钥验证,不可仅在客户端验证。

  5. ProGuard 配置

    如开启代码混淆,需添加保留规则:

    kotlin 复制代码
    -keep class androidx.credentials.** { *; }
    -keep class com.google.android.libraries.identity.googleid.** { *; }

十、参考链接

资源 链接
官方文档(新版) developer.android.com/identity/si...
官方文档(旧版) developer.android.google.cn/identity/le...
参考博客 blog.csdn.net/zll18201518...
Google Cloud Console console.cloud.google.com/
项目源码 TestGoogleSdk.java

文档版本 : V2.0
最后更新 : 2026-05-27
维护者: Android 开发团队

相关推荐
ha_lydms2 小时前
AnalyticDB分区、分布键性能优化
android·大数据·分布式·性能优化·分布式计算·分区·analyticdb
星辰2 小时前
Ijkplayer重新编译支持h264裸流
android
测试开发-学习笔记3 小时前
Android studio安装
android·ide·android studio
宋拾壹3 小时前
同时添加多个类目
android·开发语言·javascript
●VON4 小时前
AtomGit Flutter鸿蒙客户端:数据模型
android·服务器·安全·flutter·harmonyos·鸿蒙
火柴就是我4 小时前
记录一个文本随手指缩放的功能
android
Zender Han5 小时前
Android APK 签名 v1、v2、v3、v4 有什么区别?
android
神仙别闹5 小时前
基于 PHP + MySQL学生信息管理系统
android·mysql·php
墨狂之逸才6 小时前
Android 保活机制详解 —— 从概念到实践
android
故渊at6 小时前
第二板块:Android 四大组件标准化学理 | 第十二篇:四大组件全景总结与系统服务(System Server)架构
android·架构·wpf·四大组件·system service