Gmail REST API:400坏请求+前置条件失败

23

我正在尝试使用Google Java API服务基于Gmail REST API发送邮件。我已经通过Google Developer Console配置了应用程序客户端并下载了p12和json文件。

我已经使用了这个示例程序:https://developers.google.com/gmail/api/guides/sending#sending_messages...

这个示例可以工作,但它是基于GoogleAuthorizationCodeFlow的。我想直接从服务器调用,而不是打开浏览器获得访问令牌... 我已经得到了(访问令牌),但最后我收到了“Bad Request”的错误信息...为什么???我没有收到更多的信息,只有“Bad Request”和“Precondition Failed”

根据这个情况,我按照以下步骤进行:

  1. 第一步:基于我的客户端帐户邮件和生成的p12文件创建GoogleCredential对象:

    GoogleCredential  credential = new GoogleCredential.Builder().setTransport(new NetHttpTransport())
                                        .setJsonFactory(new JacksonFactory())
                                        .setServiceAccountId(serviceAccountUserEmail)
                                        .setServiceAccountScopes(scopes)
                                        .setServiceAccountPrivateKeyFromP12File(
                                                    new java.io.File(SERVICE_ACCOUNT_PKCS12_FILE_PATH))                             
                                                            .build();
    

我需要指出的是,使用ClientID而不是ClientMail会导致很多问题。必须使用@developer.gserviceaccount.com帐户,而不是.apps.googleusercontent.com。如果您没有发送这些参数,则会收到“INVALID GRANT”错误。在此处进行了说明:https://developers.google.com/analytics/devguides/config/mgmt/v3/mgmtAuthorization

  1. 第二步:基于凭据创建Gmail服务:

Gmail gmailService = new Gmail.Builder(httpTransport,
                                              jsonFactory,
                                              credential)
                                            .setApplicationName(APP_NAME)
                                                .build();
  • 第三步 从MimeMessage创建一个Google原始消息:

  • private static Message _createMessageWithEmail(final MimeMessage email) throws MessagingException, IOException {
    
    ByteArrayOutputStream bytes = new ByteArrayOutputStream();
    email.writeTo(bytes);
    String encodedEmail =       Base64.encodeBase64URLSafeString(bytes.toByteArray());      
    Message message = new Message();    
    message.setRaw(encodedEmail);
    return message;
    

  • 第四步 调用服务:

        Message message = _createMessageWithEmail(email);
    message = service.users()
    .messages()
    .send(userId,message)
    .execute();
    
  • 第五步:执行完成后获取结果...这里我收到了异常:
  • Exception in thread "main"

    com.google.api.client.googleapis.json.GoogleJsonResponseException: 400 Bad Request
    {
      "code" : 400,
      "errors" : [ {
        "domain" : "global",
        "message" : "Bad Request",
        "reason" : "failedPrecondition"
      } ],
      "message" : "Bad Request"
    }
        at com.google.api.client.googleapis.json.GoogleJsonResponseException.from(GoogleJsonResponseException.java:145)
    

    有任何想法是哪里出了问题或是哪个前提条件失败了吗?


    1
    你有没有找出问题出在哪里?我也遇到了同样的问题。 - botenvouwer
    2个回答

    30
    这是我如何在Google Apps域中使其正常工作的方法:
    1.使用Google Apps用户打开开发者控制台 2.创建一个新的项目(例如MyProject
    3.转到[API和身份验证]>[凭据]并创建一个新的[服务帐户]客户端ID
    4.复制[服务帐户]的[客户端ID](类似于xxx.apps.googleusercontent.com),以备后用
    5.现在,您必须向服务帐户委派域范围的权限,以便授权您的应用程序代表Google Apps域中的用户访问用户数据...因此,请转到您的Google Apps域管理员控制台 6.转到[安全性]部分并找到[高级设置](可能会隐藏,所以您需要单击[显示更多..]
    7.单击[管理API客户端访问]

    8.- 将之前复制到 [4] 的 [Client ID] 粘贴到 [Client Name] 文本框中。

    9.- 为了授予您的应用程序对 Gmail 的完全访问权限,请在 [API Scopes] 文本框中输入:https://mail.google.com, https://www.googleapis.com/auth/gmail.compose, https://www.googleapis.com/auth/gmail.modify, https://www.googleapis.com/auth/gmail.readonly (非常重要,请输入所有范围)


    现在您已完成所有设置... 现在是代码部分:
    1. 创建一个HttpTransport。
    private static HttpTransport _createHttpTransport() throws GeneralSecurityException,
                                                               IOException { 
        HttpTransport httpTransport = new NetHttpTransport.Builder()                                                 
                                                          .trustCertificates(GoogleUtils.getCertificateTrustStore())
                              .build();
        return httpTransport;
    }
    

    2. - 创建一个JSonFactory

    private static JsonFactory _createJsonFactory() {
        JsonFactory jsonFactory = new JacksonFactory();
        return jsonFactory;
    }
    

    3. - 创建 Google 凭据

    private static GoogleCredential _createCredentialUsingServerToken(final HttpTransport httpTransport,
                                                                      final JsonFactory jsonFactory) throws IOException,
                                                                                                            GeneralSecurityException {
        // Use the client ID when making the OAuth 2.0 access token request (see Google's OAuth 2.0 Service Account documentation).
        String serviceAccountClientID = "327116756300-thcjqf1mvrn0geefnu6ef3pe2sm61i2q.apps.googleusercontent.com"; 
    
        // Use the email address when granting the service account access to supported Google APIs 
        String serviceAccountUserEmail = "xxxx@developer.gserviceaccount.com";
    
        GoogleCredential credential = new GoogleCredential.Builder()
                                                    .setTransport(httpTransport)
                                                    .setJsonFactory(jsonFactory)
                                                    .setServiceAccountId(serviceAccountUserEmail)    // requesting the token
                                                    .setServiceAccountPrivateKeyFromP12File(new File(SERVER_P12_SECRET_PATH))
                                                    .setServiceAccountScopes(SCOPES)    // see https://developers.google.com/gmail/api/auth/scopes
                                                    .setServiceAccountUser("user@example.com")
                                                    .build();    
        credential.refreshToken();
        return credential;
    }
    

    注意:在setServiceAccountUser()方法中,使用您Google应用域中的任何用户。

    4.创建Gmail服务

    private static Gmail _createGmailService(final HttpTransport httpTransport,
                                             final JsonFactory jsonFactory,
                                             final GoogleCredential credential) {
        Gmail gmailService = new Gmail.Builder(httpTransport,
                                                jsonFactory,
                                                credential)
                                       .setApplicationName(APP_NAME)
                                       .build();
        return gmailService;
    }
    

    现在您可以像https://developers.google.com/gmail/api/guides/sending中描述的那样,对GmailService进行任何想做的操作,例如发送电子邮件;-)。请注意保留HTML标签。

    没有第五步,无法使用服务账户来下载普通 Gmail 账户的邮件/联系人,对吗?我的意思是针对普通 Gmail 账户而不是域账户。 - Rahatur
    8
    第5步似乎需要使用G-suite。我只需要访问个人电子邮件,不需要更多的东西。这对于应该是一个简单需求的问题来说,是一个沉重、复杂和昂贵的解决方案。 - ChrisR
    1
    非常感谢!此外,对于未来的读者:需要参数“sub”才能读取电子邮件。 - OZ_
    3
    在第4步中,你说“类似于xxx.apps.googleusercontent.com的那个”。 “服务帐号ID”看起来像这样(像一个电子邮件),但是如果你将其粘贴到第8步中,它似乎会列出客户端ID(一个数字)和作用域,但你会收到“failedPrecondition”和“bad request”的错误消息,并且如果你尝试模拟,则会提示“客户端未经授权使用此方法检索访问令牌”。解决方法是在第8步中使用客户端ID(一个数字),而不是服务帐号ID(看起来像是一个电子邮件)。 - NateS
    1
    不确定这是否需要另一个完整的答案,但作为更新(5/2018 v1.23.0),我还需要将ServiceAccountPrivateKeyId和ServiceAccountProjectId添加到凭据中才能使其正常工作。我发现NateS提到的表单问题现在已经正确解决了 - 您可以粘贴xxxx@developer.gserviceaccount.com,它会将其转换为客户端ID(数字),然后它就可以工作了。 - Scott A Miller
    显示剩余4条评论

    4
    非常感谢您的详细答复。我试图将其应用于asp.net MVC,但是按照给定步骤后,我的代码仍然出现了相同的错误。
    在花费了两天的时间来解决这个问题后,我通过以下方式解决了它。在ServiceAccountCredential.Initializer构造函数中,您需要使用此服务帐户指定要模拟的用户ID。
        ServiceAccountCredential credential = new ServiceAccountCredential(
                   new ServiceAccountCredential.Initializer(serviceAccountEmail)
                   {
                       User = "<USER@YOUR_GSUIT_DOMAIN.COM>",
                       Scopes = new[] { GmailService.Scope.MailGoogleCom }
                   }.FromPrivateKey(<YOUR_PRIVATE_KEY>);
    
        if (credential != null)
                    {
                        var service = new GmailService(new BaseClientService.Initializer
                        {
                            HttpClientInitializer = credential,
                            ApplicationName = "Enquiry Email Sender"
                        });
        
                        var list = service.Users.Messages.List("me").Execute();
                        var count = "Message Count is: " + list.Messages.Count();
        
                        return count;
        
                    }
    

    在Python函数中,关键字是“subject”。 - aikipooh
    Python的详细答案:https://dev59.com/j7Lma4cB1Zd3GeqPgcyZ#55367351 - Jerther

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接