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

112 阅读8分钟

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 中添加以下依赖:

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

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 类图

┌─────────────────────────────────────┐
│          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 中调用:

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

内部实现详解

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 发起登录

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

Step 1:配置 GetSignInWithGoogleOption

// 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

getCredentialRequest = new GetCredentialRequest.Builder()
    .addCredentialOption(signInWithGoogleOption)
    .build();

Step 3:异步获取凭证

// 创建取消信号(用于用户主动取消操作)
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 处理登录结果

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 登出

// 点击登出按钮时调用
TestGoogleSdk.getInstance().logout(this);

内部实现详解

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 配置

private String clientId = "162951404116-ebkc28baqbt288h3diklgfng7i00l34j.apps.googleusercontent.com";

获取方式

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

6.2 Nonce 配置

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

// ❌ 开发环境(不安全)
String nonce = "11121";

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

Nonce 作用

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

七、登录方式对比

特性GetSignInWithGoogleOptionGetGoogleIdOption
类名GetSignInWithGoogleOptionGetGoogleIdOption
界面标准 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 版本不兼容

错误信息

Dependency 'androidx.credentials:credentials:1.7.0-alpha02' 
requires Android Gradle plugin 8.6.0 or higher.

解决:降级到稳定版

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 需切换到主线程:

    activity.runOnUiThread(() -> {
        // 更新 UI
    });
    
  3. Nonce 安全
    生产环境务必使用随机生成的 nonce,防止重放攻击。

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

  5. ProGuard 配置
    如开启代码混淆,需添加保留规则:

    -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 Consoleconsole.cloud.google.com/
项目源码TestGoogleSdk.java

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