1

我正在使用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); 

知道为什么会抛出上述异常吗?

4

2 回答 2

0

我相信您正在使用 API 密钥来获取访问令牌。相反,您需要在请求令牌时传递您的 clientID/clientSecret。也就是说,如果您不使用 Google PlayServices。

了解如何遵循OAuth2.0 授权代码流程并使用GoogleAuthorizationCodeFlow

于 2013-09-09T10:58:38.873 回答
0

Google Play 服务可能是一种新的 Google Drive 身份验证方法,但它不一定比旧方法更好。

根据这篇文章How setup Google Drive Credentials within Android App? 它仅适用于 SDK 11 (Honeycomb)。

不知道为什么,但我确认这是真的。

顺便说一句,使用最新版本的驱动 SDK (google-api-services-drive-v2-rev97-1.16.0-rc.jar) 有必要以这种方式初始化驱动对象(setJsonHttpRequestInitializer 不再存在)

//request auth by AccountManager
AccountManager mgr = AccountManager.get(mContext);
String t = obtainToken(mgr.getAuthToken(getAccount(), "oauth2:"+DriveScopes.DRIVE, null, activity, callback, null));

if (t != null) {//refresh the token
    AccountManager.get(mContext).invalidateAuthToken("com.google", t);
    t = obtainToken(mgr.getAuthToken(getAccount(), "oauth2:"+DriveScopes.DRIVE, null, activity, callback, null));
}

//token is actually provided in the initializer
//credential = new GoogleCredential().setAccessToken(t);

final String token = t;

// prepare drive
if (drive == null) {
    drive = new Drive.Builder(new NetHttpTransport(), new JacksonFactory(), null)
    .setGoogleClientRequestInitializer(new GoogleClientRequestInitializer() {

        @Override
        public void initialize(AbstractGoogleClientRequest<?> request) throws IOException {
            DriveRequest driveRequest = (DriveRequest) request;
            driveRequest.setPrettyPrint(true);
            driveRequest.setKey(SIMPLE_API_ACCESS_KEY);
            driveRequest.setOauthToken(token);
        }
    })
    .build();
}
于 2013-09-09T10:08:22.590 回答