简体   繁体   中英

Using Java and OAuth 2.0 to Connect to Google Contacts

I am having issues connecting to the Google Contacts API with Java using OAuth 2.0. After extensive searching, I have reached the point where it appears I can connect, but receive error messages in the final process.

I have done the following:

1) Created an OAuth 2.0 Service account via Google's Developers Console for the project.

2) Following the steps to 'Delegate domain-wide authority to the service account' I found at https://developers.google.com/identity/protocols/OAuth2ServiceAccount , I have authorized the Client ID of the service account created in step #1 above to the scope https://www.google.com/m8/feeds/ for the Contacts (Read/Write).

3) Here is the code I have based on other members posts:

import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.gdata.client.contacts.ContactsService;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Arrays;

public class Connector {
private static ContactsService contactService = null;
private static HttpTransport httpTransport;

private static final String APPLICATION_NAME = "MyProject";
private static final String SERVICE_ACCOUNT_EMAIL = "service-account-email-address@developer.gserviceaccount.com";
private static final java.util.List<String> SCOPE = Arrays.asList("https://www.google.com/m8/feeds");

private Connector() {
    // explicit private no-args constructor
}

public static ContactsService getInstance() {
    if (contactService == null) {
        try {
            contactService = connect();
        } catch (GeneralSecurityException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    return contactService;
}

private static ContactsService connect() throws GeneralSecurityException, IOException {
    httpTransport = GoogleNetHttpTransport.newTrustedTransport();

    java.io.File p12File = new java.io.File("MyProject.p12");

    // @formatter:off
    GoogleCredential credential = new GoogleCredential.Builder()
                                                    .setTransport(httpTransport)
                                                    .setJsonFactory(JacksonFactory.getDefaultInstance())
                                                    .setServiceAccountId(SERVICE_ACCOUNT_EMAIL)
                                                    .setServiceAccountScopes(SCOPE)
                                                    .setServiceAccountPrivateKeyFromP12File(p12File)
                                                    .setServiceAccountUser("user@example.com")
                                                    .build();
    // @formatter:on

    if (!credential.refreshToken()) {
        throw new RuntimeException("Failed OAuth to refresh the token");
    }

    ContactsService myService = new ContactsService(APPLICATION_NAME);
    myService.setOAuth2Credentials(credential);

    return myService;
}
}

In theory, I believe this is supposed to all work. However, when credential.refreshToken() is executed I receive the error:

com.google.api.client.auth.oauth2.TokenResponseException: 403 Forbidden
{
  "error" : "access_denied",
  "error_description" : "Requested client not authorized."
}

If I remove setServiceAccountUser("user@example.com") , I receive the error message com.google.gdata.util.ServiceForbiddenException: Cannot request contacts belonging to another user . I believe I need to keep setServiceAccountUser(...) but am unable to get past the token response exception.

I am unsure where to go from here.

Thank you for any advice or assistance you can provide.

For anyone interested or running into the same issue as I was having, I have come across this link which solved the issue. The main problem was the '/' I had at the end of the scope I needed to use. It needed to be added in my code to match the scope I gave to the service account API access.

My final code:

HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();

String APPLICATION_NAME = "MyProject";
String SERVICE_ACCOUNT_EMAIL = "my-service-account@developer.gserviceaccount.com";
java.io.File p12File = new java.io.File("MyProject.p12");

GoogleCredential credential = new GoogleCredential.Builder()                                                                    
      .setTransport(httpTransport)                                                     
      .setJsonFactory(jsonFactory)                                                   
      .setServiceAccountId(SERVICE_ACCOUNT_EMAIL)   
      .setServiceAccountScopes(
            Collections.singleton("https://www.google.com/m8/feeds/"))                                                          
      .setServiceAccountPrivateKeyFromP12File(p12File)                                                  
      .setServiceAccountUser("user@example.com")
      .build();

if (!credential.refreshToken()) {
    throw new RuntimeException("Failed OAuth to refresh the token");
}

ContactsService service = new ContactsService(APPLICATION_NAME);
service.setOAuth2Credentials(credential);

Query gQuery = new Query(new java.net.URL("https://www.google.com/m8/feeds/groups/user@example.com/full"));
gQuery.setMaxResults(32767);
ContactGroupFeed groupFeed = service.query(gQuery, ContactGroupFeed.class);

for (ContactGroupEntry group : groupFeed.getEntries()) {
    System.out.println("group: " + group.getTitle().getPlainText());

    Query cQuery = new Query(new java.net.URL("https://www.google.com/m8/feeds/contacts/user@example.com/full"));
    cQuery.setMaxResults(32767);
    String grpId = group.getId();
    cQuery.setStringCustomParameter("group", grpId);
    ContactFeed feed = service.query(cQuery, ContactFeed.class);

    for (ContactEntry contact : feed.getEntries()) {
        System.out.println("name: " + contact.getTitle().getPlainText());
    }
}

It appears you're using the Authorization Code flow from OAuth2 (read more in this nice post ).

In this case, after the step you already did (register your app in Google Console + register your app to access Contacts API), your app will need to:

  1. Provide to the user the authorization URL: user will click and grant to your app permission to read the data you need.
  2. With user permission granted, you'll receive an authorization code .
  3. Using this authorization code , you'll exchange it by a token .
  4. Now, with the token in your hands, call the Google Contacts API.

User Java Libraries

My advice is to use popular Java libraries that handle all this flow for you.

Two popular libraries are:

Both have Maven support.

Another possible solution is to use broad address book services like CloudSponge.com , which offers a Java API and support for other popular sources, in case you're planning beyond just Gmail.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM