繁体   English   中英

使用 android GoogleSignInApi 凭据执行 google sheet api 访问

[英]perform google sheets api access with android GoogleSignInApi credentials

我知道有几个帖子讨论了 android 的登录/oauth,但没有一个能解决我的问题。

我正在努力使用 Google 表格 v4 教程中的 AccountManager 来协调身份验证流程与 GoogleSignInApi。

使用 GoogleSignInApi 我最终得到了一个授权码。 到目前为止一切顺利。 接下来,文档建议将 authcode 交换为 authtoken/recovery 令牌。 https://developers.google.com/identity/sign-in/android/offline-access有一个很好的例子,说明如何将身份验证代码发送到后端进行交换。

此流程的唯一问题 - 我没有自己的后端,因为我只想访问 google sheet api。 sheet api 调用需要一个 GoogleCredential 对象,我无法从 authcode 或以其他方式通过 GoogleSignInAccount 对象获取该对象。

所以,我的问题:

  1. 我在哪里可以发送我通过 GoogleSignInApi 收到的身份验证代码以将其交换为身份验证令牌。
  2. 是否有一个库可以处理交换请求和刷新魔法,或者我希望捕获刷新令牌并自己发出另一个身份验证令牌请求。
  3. 有没有更好的方法来获取正确的工作表访问凭据,同时还将 GoogleSignInApi 用于 firebase 服务?
  4. 如果我最终按照服务器端访问的建议使用 GoogleAuthorizationCodeTokenRequest,在客户端中使用客户端密钥是否可以接受? 可能不是。

这是我正在尝试制作的 sheet api 调用的简化版本。

GoogleCredential credential = new GoogleCredential().setAccessToken("TEST_ACCESS_TOKEN_FROM_OAUTH_PLAYGROUND");

mService = new com.google.api.services.sheets.v4.Sheets.Builder(
                        transport, jsonFactory, credential)
                        .setApplicationName("Google Sheets API Android Quickstart")
                        .build();

更新:为了取得一些进展,我最终实现了一个服务器端流程来交换令牌。 我很确定,这不是正确的技术,因为它需要在应用程序中使用 client_secret。

第 1 部分:SignInActivity 基于 firebase 代码实验室。 我需要一个 firebase 帐户,所以我觉得我必须使用 GoogleSignInApi。

public class SignInActivity extends AppCompatActivity implements GoogleApiClient.OnConnectionFailedListener,
        View.OnClickListener {


    private static final int RC_SIGN_IN = 9001;

    private GoogleApiClient mGoogleApiClient;
    private FirebaseAuth mFirebaseAuth;

    public static final String PREF_ACCOUNT_NAME = "accountName";
    public static final String PREF_ID_TOKEN = "idToken";
    public static final String PREF_AUTH_CODE = "authCode";

    public static final Scope SHEETS_SCOPE = new Scope(SheetsScopes.SPREADSHEETS);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sign_in);

        SignInButton signInButton = (SignInButton) findViewById(R.id.sign_in_button);
        signInButton.setOnClickListener(this);

        Log.d(TAG, getString(R.string.default_web_client_id));

        GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                .requestIdToken(getString(R.string.default_web_client_id))
                .requestScopes(SHEETS_SCOPE)
                .requestServerAuthCode(getString(R.string.default_web_client_id))
                .requestEmail()
                .build();
        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .enableAutoManage(this /* FragmentActivity */, this /* OnConnectionFailedListener */)
                .addApi(Auth.GOOGLE_SIGN_IN_API, gso)
                .build();

        // Initialize FirebaseAuth
        mFirebaseAuth = FirebaseAuth.getInstance();
    }

    private void handleFirebaseAuthResult(AuthResult authResult) {
        // ...
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.sign_in_button:
                signIn();
                break;
            default:
                return;
        }
    }

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

    @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) {
            GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
            if (result.isSuccess()) {
                // Google Sign In was successful, authenticate with Firebase
                GoogleSignInAccount account = result.getSignInAccount();

                SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.getApplicationContext());
                SharedPreferences.Editor editor = prefs.edit();
                editor.putString(PREF_ACCOUNT_NAME, account.getEmail());
                editor.putString(PREF_ID_TOKEN, account.getIdToken());
                editor.putString(PREF_AUTH_CODE, account.getServerAuthCode());               
                editor.apply();

                // TODO: it would be great to do the exchange of the authcode now but it's doing a
                // network call and can't be on the main thread.

                // I really need this one
                firebaseAuthWithGoogle(account);
            } else {
                // Google Sign In failed
                Log.e(TAG, "Google Sign In failed.");
            }
        }
    }

    private void firebaseAuthWithGoogle(GoogleSignInAccount acct) {
        Log.d(TAG, "firebaseAuthWithGoogle:" + acct.getId());
        AuthCredential credential = GoogleAuthProvider.getCredential(acct.getIdToken(), null);
        mFirebaseAuth.signInWithCredential(credential)
                .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
                    @Override
                    public void onComplete(@NonNull Task<AuthResult> task) {
                        Log.d(TAG, "signInWithCredential:onComplete:" + task.isSuccessful());

                        // 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.
                        if (!task.isSuccessful()) {
                            Log.w(TAG, "signInWithCredential", task.getException());
                            Toast.makeText(SignInActivity.this, "Authentication failed.",
                                    Toast.LENGTH_SHORT).show();
                        } else {
                            startActivity(new Intent(SignInActivity.this, MainActivity.class));
                            finish();
                        }
                    }
                });
    }

    @Override
    public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
        // An unresolvable error has occurred and Google APIs (including Sign-In) will not
        // be available.
        Log.d(TAG, "onConnectionFailed:" + connectionResult);
        Toast.makeText(this, "Google Play Services error.", Toast.LENGTH_SHORT).show();
    }
}

第 2 部分:DataManager 是一个实用程序类,应用程序使用它来访问工作表数据。 它不使用工作表代码实验室中推荐的流程,因为该流程不允许我使用相同的用户数据设置 firebase 帐户。

public class DataManager {

    public static final String UNDEF = "undefined";

    private com.google.api.services.sheets.v4.Sheets mService = null;
    // this is the play copy
    private static String mSheetID = SHEET_ID;

    private static final String PREF_ACCESS_TOKEN = "accessToken";
    private static final String PREF_REFRESH_TOKEN = "refreshToken";
    private static final String PREF_EXPIRES_IN_SECONDS = "expiresInSec";

    private Context mContext;
    private String mAccessToken;
    private String mRefreshToken;
    private Long mExpiresInSeconds;
    private String mAuthCode;

    public DataManager(Context context) {
        mContext = context;

        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
        mAuthCode = prefs.getString(SignInActivity.PREF_AUTH_CODE, UNDEF);
        mAccessToken =  prefs.getString(PREF_ACCESS_TOKEN, UNDEF);
        mRefreshToken = prefs.getString(PREF_REFRESH_TOKEN, UNDEF);
        mExpiresInSeconds = prefs.getLong(PREF_EXPIRES_IN_SECONDS, 0);
    }

    private void exchangeCodeForToken(String authCode) {

        try {
            GoogleTokenResponse tokenResponse =
                    new GoogleAuthorizationCodeTokenRequest(
                            new NetHttpTransport(),
                            JacksonFactory.getDefaultInstance(),
                            "https://www.googleapis.com/oauth2/v4/token",
                            mContext.getString(R.string.default_web_client_id),
                            // TODO: the app shouldn't have to use the client secret
                            {CLIENT_SECRET},
                            authCode,
                            "")
                            .execute();

            mAccessToken = tokenResponse.getAccessToken();
            mRefreshToken = tokenResponse.getRefreshToken();
            mExpiresInSeconds = tokenResponse.getExpiresInSeconds();

            // TODO: do I really need to store and pass the three values individually?
            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
            SharedPreferences.Editor editor = prefs.edit();
            editor.putString(PREF_ACCESS_TOKEN, mAccessToken);
            editor.putString(PREF_REFRESH_TOKEN, mRefreshToken);
            editor.putLong(PREF_EXPIRES_IN_SECONDS, mExpiresInSeconds);
            editor.remove(SignInActivity.PREF_AUTH_CODE);
            editor.apply();

        } catch (Exception e) {
            Log.e(TAG, "Token exchange failed with " + e.getMessage());
        }
    }

    private void refreshAccessToken(String refreshToken) {
        try {
            // TODO: what to do here?
            throw new Exception("TBD");
        } catch (Exception e) {
            Log.e(TAG, "Token refresh failed with " + e.getMessage());
        }
    }

    private GoogleCredential getCredential() {

        if (mAuthCode != UNDEF) {
            exchangeCodeForToken(mAuthCode);
        }

        // TODO: handle missing or expired token
        if (mRefreshToken !=  UNDEF && mExpiresInSeconds < 30) {
            refreshAccessToken(mRefreshToken);
        }

        GoogleCredential credential = new GoogleCredential.Builder()
                .setTransport(new NetHttpTransport())
                .setJsonFactory(JacksonFactory.getDefaultInstance())
                .build();
        credential.setAccessToken(mAccessToken);
        if (mRefreshToken !=  UNDEF) {
            credential.setRefreshToken(mRefreshToken);
            credential.setExpiresInSeconds(mExpiresInSeconds);
        }

        return credential;
    }

    // Set up credential and service object, then issue api call.
    public ArrayList<Foo> getFooListFromServer() throws IOException {
        try {
            GoogleCredential credential = getCredential();

            HttpTransport transport = AndroidHttp.newCompatibleTransport();
            JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
            mService = new com.google.api.services.sheets.v4.Sheets.Builder(
                    transport, jsonFactory, credential)
                   .setApplicationName(mContext.getString(R.string.app_name))
                    .build();

            return getDataFromServer();
        } catch (IOException exception) {
            // ...
            throw exception;
        } catch (Exception e) {
            Log.e(TAG, "something else is going on " + e.toString());
            throw e;
        }
    }

    /**
     * Actually fetch the data from google
     *
     * @return List of Foos
     * @throws IOException
     */
    private ArrayList<Foo> getDataFromServer() throws IOException {

        ArrayList<Foo> foos = new ArrayList<Foo>();

        ValueRange response = this.mService.spreadsheets().values()
                .get(mSheetID, mRange)
                .setValueRenderOption("UNFORMATTED_VALUE")
                .setDateTimeRenderOption("FORMATTED_STRING")
                .execute(); 
        //...
        return foos;
    }
}

如果您使用Android Quickstart for Sheets API ,您的凭据问题将很容易避免。

以下是指南中提到的步骤:

Step 1: Acquire a SHA1 fingerprint
Step 2: Turn on the Google Sheets API
Step 3: Create a new Android project
Step 4: Prepare the project
Step 5: Setup the sample

OAuth 客户端 ID 可在Google Dev Console 中找到

我知道我对这个答案已经很晚了……但我只是想帮助大家。 经过 3 天的 RnD,终于我通过 google 登录 android 在 oAuth Google 中找到了 Refresh token is null 的工作解决方案

每个人都在使用这个:

GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                .requestIdToken(getString(R.string.default_web_client_id))
                .requestScopes(SHEETS_SCOPE)
                .requestServerAuthCode(getString(R.string.default_web_client_id))
                .requestEmail()
                .build();

再添加一个参数:

GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                .requestIdToken(getString(R.string.default_web_client_id))
                .requestScopes(SHEETS_SCOPE)
                .requestServerAuthCode(getString(R.string.default_web_client_id), true) //For offline access or background access token generation
                .requestEmail()
                .build();

在此之后,您将获得一个服务器身份验证令牌,它返回 refresh_token 和 access_token

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM