java - example - ¿Cómo accedo a una hoja de cálculo de Google desde App Engine utilizando una cuenta de servicio?
google cloud sdk documentation (1)
Tengo una aplicación en Google App Engine (alojada en appspot.com) que algunos usuarios utilizarán para extraer algunas cosas de Internet, guardarla en el almacén de datos y luego escribirla en una hoja de cálculo de Google. Estoy buscando utilizar una cuenta de servicio para acceder a la hoja de cálculo, pero me encuentro con un problema de 400 OK {"error": "invalid_grant"} cuando intento hacer algo, independientemente de si intento crear un archivo con la API de Drive o acceda a un archivo existente utilizando la API de hojas de cálculo.
Inicié tanto Drive API como Drive SDK en la consola de API y generé una clave P12. Este es mi método para construir un 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("[email protected]") // My personal e-mail address which I used to create the project
.build();
return credential;
}
El ServiceAccountID proviene de la consola de API. Intenté usar [email protected]
como mi ServiceAccountUser, así como [email protected]
. Los ámbitos que uso son los siguientes:
https://spreadsheets.google.com/feeds
https://docs.google.com/feeds
https://www.googleapis.com/auth/drive
https://www.googleapis.com/auth/drive.file
Aquí está el código que falla cuando trato de usar la API de hojas de cálculo:
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);
El código falla cuando el servicio intenta ejecutar la consulta.
Este es el código de Drive que probé que también falla:
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
No importa lo que intento, siempre termino con el mismo error de "concesión inválida". Aquí hay una stacktrace parcial:
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)
Intenté buscar respuestas en todas partes, incluida, entre otras, la Guía de hojas de cálculo de Google Developers , esta pregunta sobre desbordamiento de pila relacionada con la cuenta de servicio y esta pregunta sobre desbordamiento de pila específica de Google App Engine . Lo que sucede con los desarrollos (por ejemplo, Documentos -> Drive) es difícil encontrar información actualizada, no importa información que pertenezca específicamente a GAE.
Utilizo la cuenta de servicio integrada de la aplicación App Engine en lugar de crear la mía propia; De esta forma, la clave privada es guardar (no almacenada localmente, solo en el motor de la aplicación).
Para Drive:
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;
}
Para hoja de cálculo:
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);
}
Asegúrate de creds.refreshToken()
o reinicia cuando caduquen.