6

EDIT: Originally this question asked how I could authenticate with the Google Analytics API using only my API key. As vlatko pointed out, this isn't possible. Now I'm just focused on getting OAuth2 to work. I will be trying vlatko's suggestions when I get a chance and will update the question. In the meantime, feel free to contribute answers with anything you think I'm missing.


ORIGINAL QUESTION:

I'm trying to make requests to the Google Analytics API. I'm walking through the Hello Analytics tutorial trying to replicate the steps. Whatever I try, I can't seem to authenticate succesfully.

The tutorial says the following:

Open the file you created named HelloAnalyticsApi.java and add the following method:

private static Analytics initializeAnalytics() throws Exception {
    // Authorization.
    Credential credential = OAuth2Native.authorize(
        HTTP_TRANSPORT, JSON_FACTORY, new LocalServerReceiver(),
        Arrays.asList(AnalyticsScopes.ANALYTICS_READONLY));

    // Set up and return Google Analytics API client.
    return Analytics.builder(HTTP_TRANSPORT, JSON_FACTORY)
        .setApplicationName("Google-Analytics-Hello-Analytics-API-Sample")
        .setHttpRequestInitializer(credential)
        .build();
  }

When a user encounters this script, the application will attempt to open the default browser and navigate the user to a URL hosted on google.com. At this point, the user will be prompted to login and grant the application access to their data. Once granted, the application will attempt to read a code from the browser window, then close the window.

The difference is that I'm trying to do this with a servlet application, and I want to use simple API access with an API key (rather than an OAuth 2.0 client ID). I know that OAuth 2.0 is recommended, but I only need to access data that I own and want to simplify the technical requirements. I based this decision on this page, which says:

An API key is a unique key that you generate using the Console. When your application needs to call an API that's enabled in this project, the application passes this key into all API requests as a key={API_key} parameter. Use of this key does not require any user action or consent, does not grant access to any account information, and is not used for authorization.

If you are only calling APIs that do not require user data, such as the Google Custom Search API, then API keys may be simpler to implement. However, if your application already uses an OAuth 2.0 access token, then there is no need to generate an API key as well. In fact, Google ignores passed API keys if an OAuth 2.0 access token is already associated with the corresponding project.

I can't find many code examples of auth flow just using the API key - most everything I've found shows using the client ID with the downloaded .p12 file, for example the GoogleCredential javadoc. The one example application I could find was Google's Books Sample app. Anyway, here's what I tried (mimicking the first request in the tutorial, which gets a list of the accounts from the management API):

Analytics analytics =
        new Analytics.Builder(httpTransport, jsonFactory, null)
        .setApplicationName("Dev API Access")
        .build();
Management.Accounts.List list =
        analytics.management().accounts().list().setKey(apiKey);
Accounts accounts = list.execute();

Where "Dev API Access" is the "Name" field in my API console dashboard. The API key is a server key restricted to my IP address. This fails with the following response:

401 Unauthorized
{
  "code": 401,
  "errors": [
    {
      "domain": "global",
      "location": "Authorization",
      "locationType": "header",
      "message": "Login Required",
      "reason": "required"
    }
  ],
  "message": "Login Required"
}

I also tried this:

Analytics analytics =
        new Analytics.Builder(httpTransport, jsonFactory, null)
        .setApplicationName("Dev API Access")
        .setGoogleClientRequestInitializer(new AnalyticsRequestInitializer(apiKey))
        .build();

Management.Accounts.List list = analytics.management().accounts().list();
Accounts accounts = list.execute();

Which shows the same error. What am I doing wrong here? Is OAuth2 required for analytics calls? If so, why does just using the API key work in the Books Sample app?


Moving on, I went ahead and tried OAuth2 anyway - I created a client ID and downloaded the .p12 private key file. But I couldn't get that working either. Here's what I tried:

Credential credential =
        new GoogleCredential.Builder()
        .setTransport(httpTransport)
        .setJsonFactory(jsonFactory)
        .setServiceAccountId(serviceAccountId)
        .setServiceAccountScopes(AnalyticsScopes.ANALYTICS_READONLY)
        .setServiceAccountPrivateKeyFromP12File(new File(p12FilePath))
        .setServiceAccountUser(serviceAccountUser)
        .build();

Analytics analytics =
        new Analytics.Builder(httpTransport, jsonFactory, credential)
        .setApplicationName("Dev API Access")
        .build();

Management.Accounts.List list = analytics.management().accounts().list();
Accounts accounts = list.execute();

Where serviceAccountId is the email address of the Google account owning the project and serviceAccountUser is the email address listed on the generated client ID. This fails with the following:

400 Bad Request
{
  "error": "invalid_grant"
}

What does "invalid grant" mean, and how do I successfully authenticate (ideally without OAuth2)?

4

2 回答 2

3

To answer your first question: in general, OAuth2.0 is used for authorized access to user's private data, so getting user consent and obtaining an access token is required. In the case with Google Books API, however, if you're accessing public data, there is no need for end user consent so an API key is sufficient. If you try accessing non public data with the Books API, you'll still need an OAuth2 token.

The good news for your case is that even with OAuth2, you can bypass user involvement and streamline your flow with Service Accounts - assuming your application has access to the API. There is a way to set that up for the Analytics API, explained here (check the steps in the Service Accounts section). I think you are on the right track with your Credential builder, but I don't think you need to set the service account user in there, since you are not doing any user impersonation.

于 2012-12-12T06:40:49.153 回答
3

vlatko's answer got me on the right track. The issue turned out to be that I was confusing the owner email address with the service account email address. For example, I was doing the following:

Credential credential =
    new GoogleCredential.Builder()
    .setTransport(httpTransport)
    .setJsonFactory(jsonFactory)
    .setServiceAccountId("owneremail@gmail.com")                   //wrong
    .setServiceAccountScopes(AnalyticsScopes.ANALYTICS_READONLY)
    .setServiceAccountPrivateKeyFromP12File(new File(p12FilePath))
    .setServiceAccountUser("xxx@developer.gserviceaccount.com")    //unnecessary
    .build();

When I needed to do this:

Credential credential =
    new GoogleCredential.Builder()
    .setTransport(httpTransport)
    .setJsonFactory(jsonFactory)
    .setServiceAccountId("xxx@developer.gserviceaccount.com")
    .setServiceAccountScopes(AnalyticsScopes.ANALYTICS_READONLY)
    .setServiceAccountPrivateKeyFromP12File(new File(p12FilePath))
    .build();

Also, I had added owneremail@gmail.com as a user on my Analytics application - this similarly needed to be the service account email instead.

于 2012-12-13T21:22:12.683 回答