使用 Firebase 身份验证进行身份验证后检索 Google 访问令牌

Posted

技术标签:

【中文标题】使用 Firebase 身份验证进行身份验证后检索 Google 访问令牌【英文标题】:Retrieve Google Access Token after authenticated using Firebase Authentication 【发布时间】:2017-04-11 19:29:53 【问题描述】:

我正在尝试检索 Google 访问令牌以从经过身份验证的用户(使用 Firebase 身份验证)访问 Google REST API,例如 YouTube Data API。

借助 Firebase-UI for android - Auth 库,我已成功将 Google 登录集成到我的应用中。从 FirebaseUser.getToken() 方法检索到的令牌不是 REST API 的有效 Google 访问令牌。

user.getToken(true).addOnCompleteListener(new OnCompleteListener<GetTokenResult>() 
    public void onComplete(@NonNull Task<GetTokenResult> task) 
        if (task.isSuccessful()) 
            String token = task.getResult().getToken();
            // 'token' is not a Google Access Token
        
    
);

在Google Sign-In for Web guide中,可以通过调用var token = result.credential.accessToken;获取访问令牌,但是我在Android中找不到类似的方法。

任何输入?如果我没有提供足够的信息,请在此处发表评论。谢谢你:)

【问题讨论】:

您是否从控制台启用了 API 哪个控制台@KrunalKapadiya?我已在 Firebase 身份验证控制台中启用 Google console.developers.google.com/apis/…,启用 YouTube 数据 API v3 更多详情可以访问此链接github.com/Krunalkapadiya/Youtube-Player-Android @KrunalKapadiya 是的,我已经启用并使用了YouTube Data API v3 一些可以在没有访问令牌但只有 API 密钥的情况下运行的方法。有关更多信息,我正在尝试获取由授权用户管理的频道,并且我认为 YouTube Android Player API 没有该方法(如果我错了,请纠正我),这就是为什么我需要访问 REST API 来获取该数据的原因。 【参考方案1】:

你的做法会给你firebase id令牌,见here。


您会在 firebase 中遇到三种类型的令牌:

Firebase ID 令牌

由 Firebase 在用户登录 Firebase 应用时创建。这些令牌是经过签名的 JWT,可以安全地识别 Firebase 项目中的用户。这些令牌包含用户的基本个人资料信息,包括用户的 ID 字符串,该字符串对于 Firebase 项目是唯一的。因为可以验证 ID 令牌的完整性,您可以将它们发送到后端服务器以识别当前登录的用户。

身份提供者令牌

由联合身份提供商(例如 Google 和 Facebook)创建。这些令牌可以有不同的格式,但通常是 OAuth 2.0 访问令牌。 Firebase 应用使用这些令牌来验证用户是否已成功通过身份提供者进行身份验证,然后将其转换为 Firebase 服务可用的凭据。

Firebase 自定义令牌

由您的自定义身份验证系统创建,允许用户使用您的身份验证系统登录 Firebase 应用。自定义令牌是使用服务帐户的私钥签名的 JWT。 Firebase 应用使用这些令牌的方式与使用联合身份提供商返回的令牌非常相似。


现在,你得到的是 firebase Id 令牌,你需要的是身份提供者令牌。

获取身份提供者令牌很简单,它只是您显示的步骤的前一步。

所以,提到了我们使用 firebase 登录 google 的方式here。

我将在下面添加在 UI 中显示一个按钮的完整代码,点击该按钮后,用户将登录到 Google 帐户。然后我将获取 google 访问令牌,然后将其发送到 firebase,然后将其转换为 firebase 令牌 ID。

我想你已经配置了安卓应用进行谷歌登录,如果没有,你可以详细了解here。


(简而言之,如果您已经完成设置,请查看下面的第 5 步。)
代码

    配置 Google SignIn 和 GoogleApiClient

     // Configure sign-in to request the user's ID, email address, and basic
     // profile. ID and basic profile are included in DEFAULT_SIGN_IN.
     GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
        .requestIdToken(getString(R.string.default_web_client_id))
        .requestEmail()
        .build();
    
     // NOTE : 
     // The string passed to requestIdToken, default_web_client_id, 
     // can be obtained from credentials page (https://console.developers.google.com/apis/credentials).
     // There mentioned Web application type client ID is this string.
    
    
     // ... 
     // Build a GoogleApiClient with access to the Google Sign-In API and the
     // options specified by gso.
     mGoogleApiClient = new GoogleApiClient.Builder(this)
        .enableAutoManage(this /* Activity */, this /* OnConnectionFailedListener */)
        .addApi(Auth.GOOGLE_SIGN_IN_API, gso)
        .build();
    

    将 Google 登录按钮添加到您的应用中

    <com.google.android.gms.common.SignInButton
        android:id="@+id/sign_in_button"
        android:layout_
        android:layout_ />
    

    设置登录点击监听器

    findViewById(R.id.sign_in_button).setOnClickListener(new OnClickListener() 
        public void onClick(View v)
            Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(mGoogleApiClient);
            startActivityForResult(signInIntent, RC_SIGN_IN);   
        
    );
    

    在 Activity 中重写 OnActivityResult 方法

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) 
        super.onActivityResult(requestCode, resultCode, data);
    
        // Result returned from launching the Intent from GoogleSignInApi.getSignInIntent(...);
        if (requestCode == RC_SIGN_IN) 
            // Google Sign In was successful, authenticate with Firebase
            GoogleSignInAccount account = result.getSignInAccount();
            firebaseAuthWithGoogle(account); // This method is implemented in step 5.
         else 
            // Google Sign In failed, update UI appropriately
            // ...
        
    
    

    使用 Google SignInAccount 进行 Firebase 身份验证

    String idTokenString = "";
    ...
    private void firebaseAuthWithGoogle(GoogleSignInAccount acct) 
        Log.d(TAG, "Google User Id :" + acct.getId());
    
        // --------------------------------- //
        // BELOW LINE GIVES YOU JSON WEB TOKEN, (USED TO GET ACCESS TOKEN) : 
        Log.d(TAG, "Google JWT : " + acct.getIdToken());
        // --------------------------------- //
    
        // Save this JWT in global String : 
        idTokenString = acct.getIdToken();
    
        AuthCredential credential = GoogleAuthProvider.getCredential(acct.getIdToken(), null);
        mAuth.signInWithCredential(credential)
            .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() 
                @Override
                public void onComplete(@NonNull Task<AuthResult> task) 
                    Log.d(TAG, "signInWithCredential:onComplete:" + task.isSuccessful());
    
                    if(task.isSuccessful())
                        // --------------------------------- //
                        // BELOW LINE GIVES YOU FIREBASE TOKEN ID : 
                        Log.d(TAG, "Firebase User Access Token : " + task.getResult().getToken());
                        // --------------------------------- //
                    
                    // If sign in fails, display a message to the user. If sign in succeeds
                    // the auth state listener will be notified and logic to handle the
                    // signed in user can be handled in the listener.
                    else 
                        Log.w(TAG, "signInWithCredential", task.getException());
                        Toast.makeText(GoogleSignInActivity.this, "Authentication failed.",
                                Toast.LENGTH_SHORT).show();
                    
                
            );
    
    

    最后一步:为 Firebase 验证侦听器

    private FirebaseAuth mAuth;
    private FirebaseAuth.AuthStateListener mAuthListener;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) 
        // ...
        mAuth = FirebaseAuth.getInstance();
        mAuthListener = new FirebaseAuth.AuthStateListener() 
            @Override
            public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) 
                FirebaseUser user = firebaseAuth.getCurrentUser();
                if (user != null) 
                    // User is signed in
                    Log.d(TAG, "onAuthStateChanged:signed_in:" + user.getUid());
                 else 
                   // User is signed out
                   Log.d(TAG, "onAuthStateChanged:signed_out");
                
                // ...
            
       ;
       // ...
    
    
    //...
    
    @Override
    public void onStart() 
        super.onStart();
        mAuth.addAuthStateListener(mAuthListener);
    
    
    @Override
    public void onStop() 
        super.onStop();
        if (mAuthListener != null) 
            mAuth.removeAuthStateListener(mAuthListener);
        
    
    

因此,您的答案在于第 5 步,即在您通过 Firebase 身份验证之前以及您在 google 登录之后进行身份验证。

希望对你有帮助!


更新:

重要的是,在步骤 1 中,您请求令牌 ID,否则在步骤 5 中,您将获得空令牌 ID。有关更多信息,请参阅here。我已更新第 1 步。


更新:

根据讨论,检索到的令牌是 JWT 令牌,如 here 所写。我们需要的是谷歌访问令牌。下面的代码使用 JWT 令牌在 OAuth 后端触发并检索此访问令牌:

(注意:我用的是okhttp 2.6.0版本,其他版本可能有不同的方法)

代码:

...
OkHttpClient client = new OkHttpClient();
RequestBody requestBody = new FormEncodingBuilder()
            .add("grant_type", "authorization_code")
            .add("client_id", "<Your-client-id>")   // something like : ...apps.googleusercontent.com
            .add("client_secret", "Your-client-secret")
            .add("redirect_uri","")
            .add("code", "4/4-GMMhmHCXhWEzkobqIHGG_EnNYYsAkukHspeYUk9E8") // device code.
            .add("id_token", idTokenString) // This is what we received in Step 5, the jwt token.
            .build();

final Request request = new Request.Builder()
        .url("https://www.googleapis.com/oauth2/v4/token")
        .post(requestBody)
        .build();

client.newCall(request).enqueue(new Callback() 
    @Override
    public void onFailure(final Request request, final IOException e) 
        Log.e(LOG_TAG, e.toString());                
    

    @Override
    public void onResponse(Response response) throws IOException 
        try 
            JSONObject jsonObject = new JSONObject(response.body().string());
            final String message = jsonObject.toString(5);
            Log.i(LOG_TAG, message);                    
         catch (JSONException e) 
            e.printStackTrace();
        
    
);

这是具有所需访问令牌的输出:

I/onResponse: 
          "expires_in": 3600,
          "token_type": "Bearer",
          "refresh_token": "1\/xz1eb0XU3....nxoALEVQ",
          "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjQxMWY1Ym......yWVsUA",
          "access_token": "ya29.bQKKYah-........_tkt980_qAGIo9yeWEG4"
     

希望现在有帮助!

【讨论】:

感谢您的精彩回答,但acct.getIdToken() 不是 Google 访问令牌,基于 this SO answer,Google 访问令牌以 ya29.AHES... 开头,只有 60 个字符长(acct.getIdToken() 返回1088 个字符)。再次感谢您的回答!至少现在我只需要知道如何从id token 获取access token。经过快速研究,看起来我必须部署一个后端来检索访问令牌。 reference 好吧,我想我在这里弄错了,你会在这里得到的令牌是 JWT(Json Web Token)。如果您不知道 JWT 和 OAuth 令牌之间的区别,请 google 一下。您所说的 60 字符长是 OAuth 访问令牌。所以,你需要做的是使用这个 jwt 令牌来点击https://www.googleapis.com/oauth2/v4/token,现在你可以在客户端本身做这件事,不需要服务器端。我将更新我的代码以从 android 本身获取 oauth 访问令牌。感谢指出,会尽快更新。 你刚刚更新的代码来自我之前评论中的链接。我已经尝试过了,我得到了这个回复 "error": "invalid_grant", "error_description": "Bad Request" 。我应该为code 设置与您的答案相同的值还是在哪里可以得到device code?从this guide,看起来code 的值是来自授权请求的响应。现在我很困惑。哈哈 @Wilik 既然您提到了my answer here,对于您在上面的评论中的问题where can I get that device code?,即String authCode = acct.getServerAuthCode(); 目前,我实现了一个已弃用的方法来从this guide 获取访问令牌,我只需要将经过身份验证的用户的电子邮件传递给GoogleAuthUtil.getToken() 它已被弃用(我不知道为什么)但至少它有效:) 谢谢@BNK 的回答,我将再次尝试该方法并回复您。 :D【参考方案2】:

尝试GoogleAuthUtil.getToken,其中scope 类似于“oauth2:scope1 scope2 scope3”

@Override
protected void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .requestIdToken(getString(R.string.default_web_client_id))
            .requestEmail()
            .build();

    mGoogleApiClient = new GoogleApiClient.Builder(this)
            .enableAutoManage(this, this)
            .addApi(Auth.GOOGLE_SIGN_IN_API, gso)
            .build();


private void signIn() 
    Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(mGoogleApiClient);
    startActivityForResult(signInIntent, RC_SIGN_IN);


@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) 
    super.onActivityResult(requestCode, resultCode, data);

    if (requestCode == RC_SIGN_IN) 
        GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
        if (result.isSuccess()) 
            final GoogleSignInAccount account = result.getSignInAccount();

                Runnable runnable = new Runnable() 
                    @Override
                    public void run() 
                        try 
                            String scope = "oauth2:"+Scopes.EMAIL+" "+ Scopes.PROFILE;
                            String accessToken = GoogleAuthUtil.getToken(getApplicationContext(), account.getAccount(), scope, new Bundle());
                            Log.d(TAG, "accessToken:"+accessToken); //accessToken:ya29.Gl...

                         catch (IOException e) 
                            e.printStackTrace();
                         catch (GoogleAuthException e) 
                            e.printStackTrace();
                        
                    
                ;
                AsyncTask.execute(runnable);

         else 
        
    

【讨论】:

谢谢老兄:) 感谢好友保存了一天 希望我有更多的支持。感谢您的帮助。 也拯救了我的一天!谢谢!【参考方案3】:

我正在关注@vovkas 解决方案,并想让您知道,通过上次更新11.6.0,您可以更轻松地获得所需的Account,因此您可以将所有东西都放在一个方便的花花公子中 AsyncTask 可以随时重复使用:

public class GetToken extends AsyncTask<Void, Void, String> 

    private final Context context;

    public GetToken(Context context) 
        this.context = context;
    

    @Override
    protected String doInBackground(Void... voids) 
        try 
            String scope = "oauth2:" + Scopes.EMAIL + " " + Scopes.PROFILE;
            GoogleSignInAccount account = GoogleSignIn.getLastSignedInAccount(context);
            return GoogleAuthUtil.getToken(context, account.getAccount(), scope, new Bundle());
         catch (IOException e) 
            e.printStackTrace();
         catch (GoogleAuthException e) 
            e.printStackTrace();
        
        return null;
    

关键是使用GoogleSignIn.getLastSignedInAccount(context)

【讨论】:

以上是关于使用 Firebase 身份验证进行身份验证后检索 Google 访问令牌的主要内容,如果未能解决你的问题,请参考以下文章

通过 laravel API 使用 firebase 令牌身份验证

为啥我必须在用户登录后使用 firebase 对用户进行身份验证?

使用 Firebase 可调用函数时如何检索用户的身份验证数据?

使用 google firebase 和 spring 进行身份验证

如何使用 Firebase 进行“使用 Spotify 登录”身份验证系统?

成功身份验证后,Flutter Firebase 数据库权限被拒绝