使用 Java 和 OAuth 2.0 连接到 Google 联系人
Using Java and OAuth 2.0 to Connect to Google Contacts
我在使用 OAuth 2.0 连接到 Google 联系人 API 和 Java 时遇到问题。经过广泛的搜索,我已经到了可以连接的地步,但在最后的过程中收到错误消息。
我做了以下事情:
1) 通过 Google 的开发者控制台为项目创建了一个 OAuth 2.0 服务帐户。
2) 按照 'Delegate domain-wide authority to the service account' 的步骤,我在 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/ 找到了联系人 (Read/Write)。
3) 这是我根据其他成员的帖子得到的代码:
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;
}
}
从理论上讲,我相信这应该可以正常工作。但是,当执行 credential.refreshToken() 时,我收到错误消息:
com.google.api.client.auth.oauth2.TokenResponseException: 403 Forbidden
{
"error" : "access_denied",
"error_description" : "Requested client not authorized."
}
如果我删除 setServiceAccountUser("user@example.com"),我会收到错误消息 com.google.gdata.util.ServiceForbiddenException:无法请求属于另一个用户的联系人。我相信我需要保留 setServiceAccountUser(...) 但无法通过令牌响应异常。
我不确定从这里到哪里去。
感谢您提供的任何建议或帮助。
您似乎正在使用来自 OAuth2 的 授权代码 流程(阅读更多内容 in this nice post)。
在这种情况下,在您已经完成的步骤之后(在 Google 控制台中注册您的应用程序 + 注册您的应用程序以访问联系人 API),您的应用程序将需要:
- 向用户提供授权URL:用户将点击并授予您的应用读取您需要的数据的权限。
- 获得用户许可后,您将收到
authorization code
。
- 使用这个
authorization code
,您将用 token
兑换它。
- 现在,有了
token
,请呼叫 Google 联系人 API。
用户Java图书馆
我的建议是使用流行的 Java 库来为您处理所有这些流程。
两个流行的库是:
Google OAuth2 API Libraries for Java:专注于Google OAuth2 APIs;
Apache Oitu:OAuth/OAuth2 Java 中的协议实现。它还涵盖其他 "OAuth family" 相关实现,例如 JWT、JWS 和 OpenID Connect。
两者都有 Maven 支持。
另一种可能的解决方案是使用广泛的地址簿服务,例如 CloudSponge.com, which offers a Java API 并支持其他流行的来源,以防您计划不仅仅使用 Gmail。
对于任何感兴趣的人或 运行 遇到与我遇到的相同问题的人,我遇到了这个 link 解决了这个问题。主要问题是我在需要使用的范围末尾的“/”。它需要添加到我的代码中以匹配我为服务帐户 API 访问权限提供的范围。
我的最终代码:
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());
}
}
我在使用 OAuth 2.0 连接到 Google 联系人 API 和 Java 时遇到问题。经过广泛的搜索,我已经到了可以连接的地步,但在最后的过程中收到错误消息。
我做了以下事情:
1) 通过 Google 的开发者控制台为项目创建了一个 OAuth 2.0 服务帐户。
2) 按照 'Delegate domain-wide authority to the service account' 的步骤,我在 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/ 找到了联系人 (Read/Write)。
3) 这是我根据其他成员的帖子得到的代码:
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;
}
}
从理论上讲,我相信这应该可以正常工作。但是,当执行 credential.refreshToken() 时,我收到错误消息:
com.google.api.client.auth.oauth2.TokenResponseException: 403 Forbidden
{
"error" : "access_denied",
"error_description" : "Requested client not authorized."
}
如果我删除 setServiceAccountUser("user@example.com"),我会收到错误消息 com.google.gdata.util.ServiceForbiddenException:无法请求属于另一个用户的联系人。我相信我需要保留 setServiceAccountUser(...) 但无法通过令牌响应异常。
我不确定从这里到哪里去。
感谢您提供的任何建议或帮助。
您似乎正在使用来自 OAuth2 的 授权代码 流程(阅读更多内容 in this nice post)。
在这种情况下,在您已经完成的步骤之后(在 Google 控制台中注册您的应用程序 + 注册您的应用程序以访问联系人 API),您的应用程序将需要:
- 向用户提供授权URL:用户将点击并授予您的应用读取您需要的数据的权限。
- 获得用户许可后,您将收到
authorization code
。 - 使用这个
authorization code
,您将用token
兑换它。 - 现在,有了
token
,请呼叫 Google 联系人 API。
用户Java图书馆
我的建议是使用流行的 Java 库来为您处理所有这些流程。
两个流行的库是:
Google OAuth2 API Libraries for Java:专注于Google OAuth2 APIs;
Apache Oitu:OAuth/OAuth2 Java 中的协议实现。它还涵盖其他 "OAuth family" 相关实现,例如 JWT、JWS 和 OpenID Connect。
两者都有 Maven 支持。
另一种可能的解决方案是使用广泛的地址簿服务,例如 CloudSponge.com, which offers a Java API 并支持其他流行的来源,以防您计划不仅仅使用 Gmail。
对于任何感兴趣的人或 运行 遇到与我遇到的相同问题的人,我遇到了这个 link 解决了这个问题。主要问题是我在需要使用的范围末尾的“/”。它需要添加到我的代码中以匹配我为服务帐户 API 访问权限提供的范围。
我的最终代码:
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());
}
}