我知道有幾個帖子討論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對象獲取該對象。
所以,我的問題:
- 我在哪裏可以發送我通過GoogleSignInApi 收到AUTHCODE有它包含authToken交換。
- 是否有一個庫處理交換請求和刷新 魔法或我希望自己捕獲刷新令牌併發出另一個 身份驗證令牌請求。
- 有沒有更好的方式來獲取 訪問權限的正確憑據,同時也使用GoogleSignInApi進行Firebase服務?
- 如果我最終使用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;
}
}
我同意,快速入門幫助工作表訪問,我得到了沒有問題的工作。不過,我還需要使用firebase進行身份驗證才能使用其某些組件,並且快速入門使用的憑據(GoogleAccountCredential)不允許使用這些憑據。 我最初的問題是如何將工作表教程中的憑據與firebase教程(通過GoogleSignInApi獲取)結合使用。 – duffy