2016-11-21 129 views
0

我知道有幾個帖子討論android的signin/oauth,但沒有一個能夠解決我的問題。使用android GoogleSignInApi憑證執行Google表格API訪問

我正努力通過Google表格v4教程中的AccountManager使用GoogleSignInApi來協調授權流程。

使用GoogleSignInApi我最終得到一個授權碼。到現在爲止還挺好。接下來,文檔建議交換authtoken /恢復令牌的授權碼。 https://developers.google.com/identity/sign-in/android/offline-access有一個很好的例子,說明如何將驗證碼發送到後端進行交換。

此流程的唯一問題 - 我沒有自己的後端,因爲我只想訪問Google表格api。工作表調用期望GoogleCredential對象,我無法從授權碼或通過GoogleSignInAccount對象獲取該對象。

所以,我的問題:

  1. 我在哪裏可以發送我通過GoogleSignInApi 收到AUTHCODE有它包含authToken交換。
  2. 是否有一個庫處理交換請求和刷新 魔法或我希望自己捕獲刷新令牌併發出另一個 身份驗證令牌請求。
  3. 有沒有更好的方式來獲取 訪問權限的正確憑據,同時也使用GoogleSignInApi進行Firebase服務?
  4. 如果我最終使用GoogleAuthorizationCodeTokenRequest作爲建議 進行服務器端訪問,那麼在客戶端使用客戶端密鑰 是否可以接受?可能不會。

這裏是api call我試圖做的簡化版本。

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; 
    } 
} 

回答

0

如果您使用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找到。

+0

我同意,快速入門幫助工作表訪問,我得到了沒有問題的工作。不過,我還需要使用firebase進行身份驗證才能使用其某些組件,並且快速入門使用的憑據(GoogleAccountCredential)不允許使用這些憑據。 我最初的問題是如何將工作表教程中的憑據與firebase教程(通過GoogleSignInApi獲取)結合使用。 – duffy