4

我在 Google App Engine 上有一个应用程序(托管在 appspot.com 上),一些用户将使用该应用程序从 Internet 上获取一些东西,将其保存在 Datastore 中,然后将其写入 Google 电子表格。我希望使用服务帐户来访问电子表格,但是当我尝试执行任何操作时遇到 400 OK { "error" : "invalid_grant" } 问题,无论我是否尝试使用 Drive API 创建文件或使用电子表格 API 访问现有文件。

我在 API 控制台中打开了 Drive API 和 Drive SDK 并生成了 P12 密钥。这是我构建的方法GoogleCredential

private GoogleCredential getGoogleCredential() throws IOException, GeneralSecurityException {
    GoogleCredential credential = new GoogleCredential.Builder()
    .setTransport(TRANSPORT)
    .setJsonFactory(FACTORY)
    .setServiceAccountId("1511XXX90247.apps.googleusercontent.com")
    .setServiceAccountScopes(scopes)
    .setServiceAccountPrivateKeyFromP12File(
            new File("05a605b0fXXXd2a6be864be15d81a2bd629d3bd6-privatekey.p12"))
    .setServiceAccountUser("XXX@gmail.com") // My personal e-mail address which I used to create the project
    .build();
    return credential;
}

ServiceAccountID 来自 API 控制台。我已经尝试将1511XXX90247@developer.gserviceaccount.com其用作我的 ServiceAccountUser 以及myappname@appspot.gserviceaccount.com. 我使用的范围如下:

https://spreadsheets.google.com/feeds
https://docs.google.com/feeds
https://www.googleapis.com/auth/drive
https://www.googleapis.com/auth/drive.file

这是我尝试使用电子表格 API 时失败的代码:

        SpreadsheetService service = new SpreadsheetService("name of my app");
        service.setOAuth2Credentials(getGoogleCredential());
        FeedURLFactory factory = FeedURLFactory.getDefault();
        SpreadsheetQuery query = new SpreadsheetQuery(factory.getSpreadsheetsFeedUrl());
        query.setTitleQuery("test");
        SpreadsheetFeed feed = service.query(query, SpreadsheetFeed.class);

当服务尝试执行查询时,代码会失败。

这是我尝试过的驱动器代码,但也失败了:

        Drive drive = new Drive.Builder(TRANSPORT, FACTORY,
                 getGoogleCredential()).build();
        com.google.api.services.drive.model.File file = new com.google.api.services.drive.model.File();
        file.setTitle("test");       
        file.setMimeType("application/vnd.google-apps.spreadsheet");
        drive.files().insert(file).execute(); // This is where the code fails

无论我尝试什么,我似乎总是以同样的“无效授权”错误告终。这是部分堆栈跟踪:

com.google.gdata.util.AuthenticationException: Failed to refresh access token: 400 OK { "error" : "invalid_grant" } 
at com.google.gdata.client.GoogleAuthTokenFactory$OAuth2Token.refreshToken(GoogleAuthTokenFactory.java:260) 
at com.google.gdata.client.GoogleAuthTokenFactory.handleSessionExpiredException(GoogleAuthTokenFactory.java:702) 
at com.google.gdata.client.GoogleService.handleSessionExpiredException(GoogleService.java:738)    at com.google.gdata.client.GoogleService.getFeed(GoogleService.java:680) 
at com.google.gdata.client.Service.query(Service.java:1237) 
at com.google.gdata.client.Service.query(Service.java:1178) 
at spam.gwt.scraper.server.spreadsheets.APIConnector.doGet(APIConnector.java:66)

我试图到处寻找答案,包括但不限于Google Developers Spreadsheets Guide、这个Service Account-related Stack Overflow question和这个Google App Engine-specific Stack Overflow question。随着发展(例如 Docs -> Drive)的发展,很难找到最新的信息,更不用说专门与 GAE 相关的信息了。

4

1 回答 1

2

我使用 App Engine 应用程序的内置服务帐户,而不是创建自己的;这种方式保存私钥(不存储在本地,仅在应用程序引擎上)。

对于驱动器:

    protected Drive createDriveService() throws BookingSheetException {

      HttpTransport httpTransport = new NetHttpTransport();
      JsonFactory jsonFactory = new JacksonFactory();
      GoogleClientRequestInitializer keyInitializer =
              new CommonGoogleClientRequestInitializer(API_KEY);
      AppIdentityCredential credential =
              new AppIdentityCredential.Builder(Arrays.asList(DriveScopes.DRIVE)).build();
      Drive service = new Drive.Builder(httpTransport, jsonFactory, null)
          .setHttpRequestInitializer(credential)
          .setGoogleClientRequestInitializer(keyInitializer)
          .setApplicationName(appname).build();
      return service;
}

对于电子表格:

private Credential creds;
/**
 * Keep the expiration time of the access token to renew it before expiry
 */
private Calendar expirationTime = Calendar.getInstance();
protected void initCredentials() {
    List<String> scopes = Arrays.asList("https://spreadsheets.google.com/feeds");
    AppIdentityService appIdentity = AppIdentityServiceFactory.getAppIdentityService();
    AppIdentityService.GetAccessTokenResult accessToken = appIdentity.getAccessToken(scopes);
    expirationTime.setTime(accessToken.getExpirationTime());
    expirationTime.add(Calendar.MINUTE,-REFRESH_MINUTES); //refresh some time before expiry
    creds = new Credential(BearerToken.authorizationHeaderAccessMethod());
    creds.setAccessToken(accessToken.getAccessToken());
}

public SpreadsheetUtil(String appname) {
    myService = new SpreadsheetService(appname);
    myService.setOAuth2Credentials(cred);
}

确保creds.refreshToken()在它们过期时重新初始化。

于 2015-05-26T21:59:26.680 回答