如何使用服务帐户从App Engine访问Google电子表格?

时间:2013-06-29 13:32:44

标签: java google-app-engine google-drive-api google-spreadsheet-api

我在Google App Engine上有一个应用程序(托管在appspot.com上),一些用户将使用该应用程序从互联网上获取一些内容,将其保存在数据存储区中,然后将其写入Google电子表格。我想使用服务帐户访问电子表格但是当我尝试执行任何操作时遇到400 OK {“error”:“invalid_grant”}问题,无论我是否尝试使用Drive API创建文件或使用Spreadsheets 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

以下是我尝试使用Spreadsheets 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的信息了。

1 个答案:

答案 0 :(得分:2)

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

对于云端硬盘:

    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()或过期后重新初始化。