使用 GoogleApiClient 静默登录以检索令牌

Posted

技术标签:

【中文标题】使用 GoogleApiClient 静默登录以检索令牌【英文标题】:Silent sign in to retrieve token with GoogleApiClient 【发布时间】:2016-04-26 08:28:03 【问题描述】:

我在我的应用中使用“Google 登录”。因此,我使用类 GoogleApiClient 来获取后端所需的用户电子邮件和 ID 令牌。

当用户登录时,我可以访问一个 Activity(当然),我使用该 Activity 让 GoogleApiClient 通过调用来处理 UI 生命周期的东西 builder.enableAutoManage(myActivity, ...)

这很好用。

但是,在稍后的阶段(几天后),我需要获得一个新的令牌(出于某种原因,我在此不再赘述)。我想得到这个令牌 静默,无需用户交互。但是,在我需要这个新令牌的代码中,我无法访问任何 Activity 实例。这意味着我无法 拨打上述电话,即“builder.enableAutoManage”。而且我发现如果我不打那个电话,那么静默登录似乎不起作用。

我附上了下面的代码。现在,看看“silentLogin”方法。只要我在用户实际登录时收到的令牌小于一小时,那么语句“pendingResult.isDone”将返回 true,并且可以接收缓存的令牌。但是,如果我在用户实际登录时收到的令牌超过一小时,则调用“pendingResult.setResultCallback”,但从未调用过“onResult”方法,我无法获得新的令牌。如果我从一个活动中执行完全相同的操作(并且由此也调用“builder.enableAutoManage”),则不会发生此问题。

那么,有谁知道我做错了什么,更重要的是 - 如何解决这个问题并在不访问活动实例的情况下获取新令牌?

我正在使用 com.google.android.gms:play-services-auth:8.4.0

package com.google.samples.quickstart.signin;

import android.content.Context;
import android.os.Bundle;
import android.util.Log;

import com.google.android.gms.auth.api.Auth;
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
import com.google.android.gms.auth.api.signin.GoogleSignInResult;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.Scopes;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.OptionalPendingResult;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Scope;

/**
 * Use this class to login with google account using the OpenId oauth method.
 */
public class GoogleLogin*** 
    private static final String TAG = GoogleLoginIdToken.class.getName();
    private static final String SERVER_CLIENT_ID = "XXXXXXXXXXXXXXXXXXXXXXXX.apps.googleusercontent.com";

    private GoogleApiClient mGoogleApiClient;
    private Context mContext;

    private GoogleLogin***(Context appContext) 
        this.mContext = appContext;
        createGoogleClient();
    

    /**
     * Performs a silent sign in and fetch a token.
     *
     * @param appContext Application context
     */
    public static void silentLogin(Context appContext) 
        GoogleLogin*** googleLoginIdToken = new GoogleLogin***(appContext);
        googleLoginIdToken.silentLogin();
    

    private void createGoogleClient() 
        GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                .requestProfile()
                .requestScopes(new Scope(Scopes.PROFILE))
                .requestIdToken(SERVER_CLIENT_ID)
                .requestEmail()
                .build();

        mGoogleApiClient = new GoogleApiClient.Builder(mContext)
                .addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() 
                    @Override
                    public void onConnectionFailed(ConnectionResult connectionResult) 
                        System.out.println("onConnectionFailed  = " + connectionResult);
                        onSilentLoginFinished(null);
                    
                )
                .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() 
                    @Override
                    public void onConnected(Bundle bundle) 
                        System.out.println("onConnected bundle = " + bundle);
                        onSilentLoginFinished(null);
                    

                    @Override
                    public void onConnectionSuspended(int i) 
                        System.out.println("onConnectionSuspended i = " + i);
                        onSilentLoginFinished(null);
                    
                ).addApi(Auth.GOOGLE_SIGN_IN_API, gso)
                .build();
    

    private void silentLogin() 
        OptionalPendingResult<GoogleSignInResult> pendingResult = Auth.GoogleSignInApi.silentSignIn(mGoogleApiClient);
        if (pendingResult != null) 
            if (pendingResult.isDone()) 
                // If the user's cached credentials are valid, the OptionalPendingResult will be "done"
                // and the GoogleSignInResult will be available instantly.
                Log.d(TAG, " ----------------  CACHED SIGN-IN ------------");
                System.out.println("pendingResult is done = ");
                GoogleSignInResult signInResult = pendingResult.get();
                onSilentLoginFinished(signInResult);
             else 
                System.out.println("Setting result callback");
                // If the user has not previously signed in on this device or the sign-in has expired,
                // this asynchronous branch will attempt to sign in the user silently.  Cross-device
                // single sign-on will occur in this branch.
                pendingResult.setResultCallback(new ResultCallback<GoogleSignInResult>() 
                    @Override
                    public void onResult(GoogleSignInResult googleSignInResult) 
                        System.out.println("googleSignInResult = " + googleSignInResult);
                        onSilentLoginFinished(googleSignInResult);
                    
                );
            
         else 
            onSilentLoginFinished(null);
        
    

    private void onSilentLoginFinished(GoogleSignInResult signInResult) 
        System.out.println("GoogleLoginIdToken.onSilentLoginFinished");
        if (signInResult != null) 
            GoogleSignInAccount signInAccount = signInResult.getSignInAccount();
            if (signInAccount != null) 
                String emailAddress = signInAccount.getEmail();
                String token = signInAccount.getIdToken();
                System.out.println("token = " + token);
                System.out.println("emailAddress = " + emailAddress);
            
        
    

【问题讨论】:

你说token,请问是什么token? 我想要从 Google API 获得的令牌是一个 ID 令牌(我认为这是您可以通过调用 GoogleSignInAccount.getIdToken() 获得的唯一令牌类型) 【参考方案1】:

是的,上面的答案是正确的。通常,任何 GoogleApiClient 都需要连接才能返回任何数据。 enableAutoManage 帮助您在 onStart() / onStop() 期间自动调用 connect() / disconnect()。如果不使用 autoManage,则需要手动 connect()。

更好的是,您应该在 finally 块中断开连接。

假设您不在 UI 线程上。

try 
    ConnectionResult result = mGoogleApiClient.blockingConnect();
    if (result.isSuccess()) 
        GoogleSignInResult googleSignInResult =
            Auth.GoogleSignInApi.silentSignIn(googleApiClient).await();
    ...
    
 finally 
    mGoogleApiClient.disconnect();

此外,稍微清理一下您的代码: 1. 从以下配置构建的 gso 与上面粘贴的代码相同:

GoogleSignInOptions gso =
   new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
        .requestIdToken(SERVER_CLIENT_ID)
        .requestEmail()
        .build();
    根据您当前的逻辑,addOnConnectionFailedListener / addConnectionCallbacks 除了 adb 日志之外没有其他帮助。也许只是将它们完全删除?

【讨论】:

谷歌文档一如既往地失败了。是否应该在尝试登录之前或之后调用 apiClient.connect 是绝对不清楚的。事实上,我认为分离唱歌和连接的新模式增加了复杂性,而不是降低了复杂性。无论如何,感谢伊莎贝拉的回答,这对我也有帮助! 找到一篇文章说GoogleApiClient会被GoogleSignInClient取代。见android-developers.googleblog.com/2017/11/… 它会自动选择当前登录的idToken过期的账户吗?【参考方案2】:

我发现了问题。我的印象是这个功能

OptionalPendingResult<GoogleSignInResult> pendingResult = Auth.GoogleSignInApi.silentSignIn(googleApiClient);

将为我连接 mGoogleApiClient(因为它返回 pending 结果)。但是,情况并非如此,为了解决上述问题,我只需要添加调用

ConnectionResult result = mGoogleApiClient.blockingConnect();

在silentLogin 方法的开头。 (当然,稍后断开连接,并确保调用是在与主线程不同的线程中进行的)

tada'

【讨论】:

【参考方案3】:

如果您将新的登录库与 Firebase 一起使用,请添加到 Isabella 和 Ola 的上述两个答案:

FirebaseAuth.getInstance().currentUser?.let 
    //create sign-in options the usual way
    val googleSignInClient = GoogleSignIn.getClient(context, gso)
    googleSignInClient.silentSignIn().addOnCompleteListener 
        val account: GoogleSignInAccount? = it.result
        //get user info from account object
    

此外,这可以从 UI 线程调用。 如果您之前登录过一次,FirebaseAuth.getInstance().currentUser 将始终返回用户对象。

【讨论】:

以上是关于使用 GoogleApiClient 静默登录以检索令牌的主要内容,如果未能解决你的问题,请参考以下文章

谷歌排行榜提交分数错误:必须连接GoogleApiClient

实施 GoogleApiClient 时遇到问题

使用 GoogleApiClient 进行 REST API 调用

GoogleApiClient 和 Drive.API 错误

强制关闭 GoogleApiClient.connect()

如何将 Googleapiclient 传递给 Google Play 游戏的另一个所有活动