我正在使用google-api-client-android2-1.10.3-beta.jar
.
我有一段代码,半年前就可以使用。就在最近,当我再次尝试运行它们时,它们不再起作用。我收到以下异常。
com.google.api.client.googleapis.json.GoogleJsonResponseException: 403 Forbidden
{
"code": 403,
"errors": [
{
"domain": "global",
"location": "Authorization",
"locationType": "header",
"message": "The authentication method used is not allowed.",
"reason": "authenticationMethod"
}
],
"message": "The authentication method used is not allowed."
}
期间抛出异常
// request = service.files().list().setQ("title contains 'jstock-" + Utils.getJStockUUID().substring(0, 19) + "' and trashed = false");
FileList files = request.execute();
我的一段代码,基本上是执行请求
https://www.googleapis.com/drive/v2/files?q=title+contains+%27jstock-fe78440e-e0fe-4efb-%27+and+trashed+%3d+false
,通过使用来自的 AuthTokencom.google.api.client.googleapis.extensions.android2.auth.GoogleAccountManager
我在 oAuth 2.0 游乐场尝试请求。它工作正常。但是,我不确定为什么我的半年前有效的代码不再有效。
这是用于获取 AuthToken 的代码
package com.jstock.cloud;
import java.io.IOException;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import com.google.api.client.googleapis.extensions.android2.auth.GoogleAccountManager;
import com.jstock.engine.Subject;
import com.jstock.gui.JStockApplication;
import com.jstock.gui.Preferences;
import com.jstock.gui.R;
// The code is picked from http://code.google.com/p/google-api-java-client/source/browse/tasks-android-sample/src/main/java/com/google/api/services/samples/tasks/android/TasksSample.java?repo=samples
public class LoginManager extends Subject<LoginManager, String> {
public LoginManager(Activity activity) {
// TODO : Need to revise API key.
ClientCredentials.errorIfNotSpecified();
// Noted, the passed in activity, should override the following method.
// protected void onActivityResult(int requestCode, int resultCode, Intent data)
// In the overriden method, it should call LoginManager's onActivityResult.
this.activity = activity;
dialog = new ProgressDialog(activity);
dialog.setMessage(activity.getString(R.string.login));
// TODO : #12 Decide usage of SharedPreferences over JStockOptions
authToken = Preferences.getAuthTOken();
accountManager = new GoogleAccountManager(JStockApplication.instance());
accountName = Preferences.getAccountName();
}
public void gotAccount() {
Account account = accountManager.getAccountByName(accountName);
if (account == null) {
chooseAccount();
return;
}
if (authToken != null) {
onAuthToken(authToken);
return;
}
dialog.show();
accountManager.getAccountManager().getAuthToken(account, AUTH_TOKEN_TYPE, true, new AccountManagerCallback<Bundle>() {
public void run(AccountManagerFuture<Bundle> future) {
try {
Bundle bundle = future.getResult();
if (bundle.containsKey(AccountManager.KEY_INTENT)) {
Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT);
intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK);
activity.startActivityForResult(intent, REQUEST_AUTHENTICATE);
return;
} else if (bundle.containsKey(AccountManager.KEY_AUTHTOKEN)) {
final String authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN);
setAuthToken(authToken);
onAuthToken(authToken);
return;
}
} catch (Exception e) {
Log.e(TAG, e.getMessage(), e);
}
onAuthToken(null);
}
}, null);
}
public void chooseAccount() {
dialog.show();
accountManager.getAccountManager().getAuthTokenByFeatures(GoogleAccountManager.ACCOUNT_TYPE,
AUTH_TOKEN_TYPE,
null,
activity,
null,
null,
new AccountManagerCallback<Bundle>() {
public void run(AccountManagerFuture<Bundle> future) {
Bundle bundle;
try {
bundle = future.getResult();
setAccountName(bundle.getString(AccountManager.KEY_ACCOUNT_NAME));
final String authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN);
setAuthToken(authToken);
onAuthToken(authToken);
return;
} catch (OperationCanceledException e) {
// user canceled
} catch (AuthenticatorException e) {
Log.e(TAG, e.getMessage(), e);
} catch (IOException e) {
Log.e(TAG, e.getMessage(), e);
}
onAuthToken(null);
}
},
null);
}
private void setAccountName(String accountName) {
// TODO : #12 Decide usage of SharedPreferences over JStockOptions
Preferences.setAccountName(accountName);
this.accountName = accountName;
}
private void setAuthToken(String authToken) {
// TODO : #12 Decide usage of SharedPreferences over JStockOptions
Preferences.setAuthToken(authToken);
this.authToken = authToken;
}
// To be consumed by this.activity.
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_AUTHENTICATE:
if (resultCode == Activity.RESULT_OK) {
gotAccount();
} else {
chooseAccount();
}
break;
}
}
private void onAuthToken(final String authToken) {
// Need to run on AsyncTask. This method is currently executed by main
// thread, as we are using null in the last parameter of
// getAuthTokenByFeatures and getAuthToken. I try not to notify through
// main thread, as I believe callers will most probably perform non-UI
// task.
new AsyncTask<Void, Void, Void>() {
@Override
protected void onPreExecute() {
dialog.dismiss();
}
@Override
protected Void doInBackground(Void... arg0) {
// authToken possible null, which indicates failure.
LoginManager.this.notify(LoginManager.this, authToken);
return null;
}
@Override
protected void onPostExecute(Void result) {
}
}.execute();
}
public void invalidateAuthToken() {
// TODO : #12 Decide usage of SharedPreferences over JStockOptions
accountManager.invalidateAuthToken(authToken);
authToken = null;
Preferences.removeAuthToken();
}
private static final String AUTH_TOKEN_TYPE = "oauth2:https://www.googleapis.com/auth/drive";
private static final int REQUEST_AUTHENTICATE = 0;
private final GoogleAccountManager accountManager;
private final Activity activity;
private String accountName;
private final ProgressDialog dialog;
private String authToken;
private static final String TAG = "LoginManager";
}
这是用于执行 API 请求的代码
package com.jstock.cloud;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import android.util.Log;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.services.GoogleKeyInitializer;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.client.extensions.android2.AndroidHttp;
import com.google.api.services.drive.Drive;
import com.google.api.services.drive.Drive.Files;
import com.google.api.services.drive.model.File;
import com.google.api.services.drive.model.FileList;
import com.jstock.gui.Utils;
public class CloudFile {
public final java.io.File file;
public final long checksum;
public final long date;
public final int version;
private CloudFile(java.io.File file, long checksum, long date, int version) {
this.file = file;
this.checksum = checksum;
this.date = date;
this.version = version;
}
public static CloudFile newInstance(java.io.File file, long checksum, long date, int version) {
return new CloudFile(file, checksum, date, version);
}
public static CloudFile loadFromGoogleDrive(String authToken) {
final HttpTransport transport = AndroidHttp.newCompatibleTransport();
final JsonFactory jsonFactory = new GsonFactory();
GoogleCredential credential = new GoogleCredential();
Log.i("CHEOK", "authToken = " + authToken);
credential.setAccessToken(authToken);
Drive service = new Drive.Builder(transport, jsonFactory, credential)
.setApplicationName(Utils.getApplicationName())
.setJsonHttpRequestInitializer(new GoogleKeyInitializer(ClientCredentials.KEY))
.build();
List<File> files = retrieveAllJStockFiles(service);
long checksum = 0;
long date = 0;
int version = 0;
File file = null;
for (File _file : files) {
file = _file;
// Use title, not filename.
final String title = file.getTitle();
final String downloadUrl = file.getDownloadUrl();
if (title == null || downloadUrl == null) {
// Do we really need to perform null checking?
continue;
}
// Retrieve checksum, date and version information from filename.
final Matcher matcher = googleDocTitlePattern.matcher(title);
String _checksum = null;
String _date = null;
String _version = null;
if (matcher.find()){
if (matcher.groupCount() == 3) {
_checksum = matcher.group(1);
_date = matcher.group(2);
_version = matcher.group(3);
}
}
if (_checksum == null || _date == null || _version == null) {
continue;
}
try {
checksum = Long.parseLong(_checksum);
date = Long.parseLong(_date);
version = Integer.parseInt(_version);
} catch (NumberFormatException ex) {
Log.e(TAG, "", ex);
continue;
}
} // for
if (file == null) {
return null;
}
final java.io.File temp = Utils.createTempFileOnExternalCacheDir(Utils.getJStockUUID(), ".zip");
if (temp == null) {
return null;
}
// Delete temp file when program exits.
temp.deleteOnExit();
// Delete temp file when program exits.
Map<String, String> headers = new LinkedHashMap<String, String>();
headers.put("Authorization", "OAuth " + authToken);
java.io.File downloadedFile = Utils.downloadAsTempFile(file.getDownloadUrl(), headers, temp);
if (downloadedFile == null) {
return null;
}
return CloudFile.newInstance(downloadedFile, checksum, date, version);
}
/**
* Retrieve a list of File resources.
*
* @param service Drive API service instance.
* @return List of File resources.
*/
private static List<File> retrieveAllJStockFiles(Drive service) {
List<File> result = new ArrayList<File>();
Files.List request = null;
try {
// Not sure why. In oAuth 2 playground, I cannot use full JStock
// UUID. Perhaps it places restriction on length of query.
// https://www.googleapis.com/drive/v2/files?q=title+contains+%27jstock-fe78440e-e0fe-4efb-%27+and+trashed+%3d+false
//request = service.files().list();
request = service.files().list().setQ("title contains 'jstock-" + Utils.getJStockUUID().substring(0, 19) + "' and trashed = false");
} catch (IOException e) {
Log.e(TAG, "", e);
return result;
}
do {
try {
FileList files = request.execute();
result.addAll(files.getItems());
request.setPageToken(files.getNextPageToken());
} catch (IOException e) {
Log.e(TAG, "", e);
request.setPageToken(null);
}
} while (request.getPageToken() != null && request.getPageToken().length() > 0);
return result;
}
// http://stackoverflow.com/questions/1360113/is-java-regex-thread-safe
private static final Pattern googleDocTitlePattern = Pattern.compile("jstock-" + Utils.getJStockUUID() + "-checksum=([0-9]+)-date=([0-9]+)-version=([0-9]+)\\.zip", Pattern.CASE_INSENSITIVE);
private static final String TAG = "CloudFile";
}
这是我们执行 API 调用的方式
// authToken is String, which obtained from LoginManager.
CloudFile.loadFromGoogleDrive(authToken);
知道为什么会抛出上述异常吗?