【问题标题】:Trying to send email via Service Account getting com.google.api.client.auth.oauth2.TokenResponseException: 401 Unauthorized尝试通过服务帐户发送电子邮件 com.google.api.client.auth.oauth2.TokenResponseException: 401 Unauthorized
【发布时间】:2017-03-22 13:48:34
【问题描述】:

我有一个 Google Apps 帐户。我正在尝试使用服务帐户代表用户发送电子邮件。

我搜索了互联网,但没有任何工作,我几乎不知所措。

我已遵循 Java 指南,但仍不断收到 com.google.api.client.auth.oauth2.TokenResponseException: 401 Unauthorized

为什么这段代码 sn-p 给我 401 Unauthorized?

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

            GoogleCredential credential = new GoogleCredential.Builder()
                    .setTransport(httpTransport)
                    .setJsonFactory(JSON_FACTORY)
                    .setServiceAccountId("something@something-something.iam.gserviceaccount.com")
                    .setServiceAccountPrivateKeyFromP12File(new File("path/to/file/myProject.p12"))
                    .setServiceAccountScopes(GmailScopes.all())
                    .setServiceAccountUser("user@mydomain.org")
                    .build();

Gmail gmailService = new Gmail.Builder(httpTransport, JSON_FACTORY, credential)
                    .setApplicationName("My App") // DOES IT MATTER WHAT THIS IS SET TO?
                    .build();

MimeMessage mimeMessage = createEmail("myemail@gmail.com", "user@mydomain.org", "Testing", "hey");
sendMessage(gmailService, "me", mimeMessage);

这些方法基本上是从 Google 的文档中复制/粘贴:

/**
     * Create a MimeMessage using the parameters provided.
     *
     * @param to email address of the receiver
     * @param from email address of the sender, the mailbox account
     * @param subject subject of the email
     * @param bodyText body text of the email
     * @return the MimeMessage to be used to send email
     * @throws MessagingException
     */
    public static MimeMessage createEmail(String to,
                                          String from,
                                          String subject,
                                          String bodyText)
            throws MessagingException {
        Properties props = new Properties();
        Session session = Session.getDefaultInstance(props, null);

        MimeMessage email = new MimeMessage(session);

        email.setFrom(new InternetAddress(from));
        email.addRecipient(javax.mail.Message.RecipientType.TO,
                new InternetAddress(to));
        email.setSubject(subject);
        email.setText(bodyText);
        return email;
    }

    /**
       * Send an email from the user's mailbox to its recipient.
       *
       * @param service Authorized Gmail API instance.
       * @param userId User's email address. The special value "me"
       * can be used to indicate the authenticated user.
       * @param email Email to be sent.
       * @throws MessagingException
       * @throws IOException
       */
      public static void sendMessage(Gmail service, String userId, MimeMessage email)
          throws MessagingException, IOException {
        Message message = createMessageWithEmail(email);
        System.out.println("userId = " + userId);
        message = service.users().messages().send(userId, message).execute();

        System.out.println("Message id: " + message.getId());
        System.out.println(message.toPrettyString());
      }

      /**
       * Create a Message from an email
       *
       * @param email Email to be set to raw of message
       * @return Message containing base64url encoded email.
       * @throws IOException
       * @throws MessagingException
       */
      public static Message createMessageWithEmail(MimeMessage email)
          throws MessagingException, IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        email.writeTo(baos);
        String encodedEmail = Base64.encodeBase64URLSafeString(baos.toByteArray());
        Message message = new Message();
        message.setRaw(encodedEmail);
        return message;
      }

我刚刚得到这个堆栈跟踪:

com.google.api.client.auth.oauth2.TokenResponseException: 401 Unauthorized
    at com.google.api.client.auth.oauth2.TokenResponseException.from(TokenResponseException.java:105)
    at com.google.api.client.auth.oauth2.TokenRequest.executeUnparsed(TokenRequest.java:287)
    at com.google.api.client.auth.oauth2.TokenRequest.execute(TokenRequest.java:307)
    at com.google.api.client.googleapis.auth.oauth2.GoogleCredential.executeRefreshToken(GoogleCredential.java:384)
    at com.google.api.client.auth.oauth2.Credential.refreshToken(Credential.java:489)
    at com.google.api.client.auth.oauth2.Credential.intercept(Credential.java:217)
    at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:859)
    at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:419)
    at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:352)
    at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.execute(AbstractGoogleClientRequest.java:469)
    at com.my.services.NotificationServiceTest.testGmailCredential(NotificationServiceTest.java:96)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

我已创建服务帐户并将其设置为域范围的委派,这是我的管理 API 客户端访问范围的屏幕截图:

我错过了什么,我一直得到 401 Unauthorized?

【问题讨论】:

  • 您的凭据文件是否包含oauth2 想要的所有数据?
  • 基于此blog,此错误的最常见原因是访问令牌已过期。这也可能是由于开发人员意外禁用了 API 和/或用户撤销了令牌。您也可以查看这个相关的thread
  • 如何知道我的访问令牌是否已过期? Gmail API 已启用。用户如何撤销服务帐户的令牌?你指的是哪个用户?
  • 将应用程序名称设置为与您在 google 控制台中注册的名称相同以获取凭据。

标签: authentication oauth google-api google-apps gmail-api


【解决方案1】:

使用访问令牌进行 API 调用时最常见的错误原因是:

  • 过期的访问令牌(最常见)
  • 开发人员意外禁用了 API(不常见)
  • 用户撤销令牌(罕见)

有时,更多解释存在于 HTTP 4xx 的响应正文中。例如,在 Java 客户端中,您应该记录错误,因为它将有助于故障排除:

try {   
       // Make your Google API call
} catch (GoogleJsonResponseException e) {
      GoogleJsonError error = e.getDetails();
      // Print out the message and errors
}

每当您收到 HTTP 4xx 并记录该响应时,您都可以使用现有代码并在此处进行 API 调用。这将返回一些有用的信息。

如果令牌无效,您可以按照以下步骤操作。

  • 从您的数据存储或数据库中删除访问令牌。
  • 使用刷新令牌获取新的访问令牌(如果您正在使用刷新令牌)
  • 尝试再次调用 API。如果它有效,你很好!如果不是……
  • 根据 tokenInfo API 检查访问令牌
  • 如果仍然无效,请重新验证

您可以通过以下this 链接调试所有内容。它甚至有一个教程视频!

【讨论】:

  • 这只是从故障排除站点复制粘贴。它不会以任何方式回答 OP 的具体问题。
  • 据我了解,服务帐户没有刷新令牌。
【解决方案2】:

查看here,您似乎还没有访问令牌!将refreshToken 添加到您的代码中:

        GoogleCredential credential = new GoogleCredential.Builder()
            .setTransport(httpTransport)
            .setJsonFactory(JSON_FACTORY)
            .setServiceAccountId("something@something-something.iam.gserviceaccount.com")
            .setServiceAccountPrivateKeyFromP12File(new File("path/to/file/myProject.p12"))
            .setServiceAccountScopes(GmailScopes.all())
            .setServiceAccountUser("user@mydomain.org")
            .build();

        credential.refreshToken();

【讨论】:

  • 这不适用于服务帐户。
【解决方案3】:

在与 Google 支持人员进行了长时间但很有帮助的电话后,我们终于发现,如果我将 GmailScopes.all() 更改为下面的显式范围,那么它就可以工作。

Collection<String> SCOPES 
    = Collections.unmodifiableCollection(
            Arrays.asList(
                    new String[]{
                            GmailScopes.GMAIL_COMPOSE,
                            GmailScopes.GMAIL_SEND
                    }));

支持人员不是 100% 确定的,但他认为可能是因为我的用户无权访问所有 gmail 范围,并且我指定 GmailScopes.all() 甚至在检查之前它是错误的 401 Unauthorized我尝试使用的范围。

【讨论】:

    猜你喜欢
    • 2017-04-14
    • 1970-01-01
    • 2017-03-16
    • 2022-06-29
    • 1970-01-01
    • 2017-10-30
    • 2017-03-13
    • 2011-05-21
    • 2020-09-07
    相关资源
    最近更新 更多