サービスアカウントで認証してGMail APIでメール送信（Java）

# はじめに
[前回](https://qiita.com/tnagao3000/items/8c1a509312ca80f3fdd1) は実行時にメール送信元アカウントに OAuth認証 させました。
今回はその代わりにいい感じに設定した [サービスアカウント](https://cloud.google.com/iam/docs/service-accounts?hl=ja) を使用します。

いい感じになったサービスアカウントはメール送信元アカウントから委譲された権限でメール送信します。
どちらの実装が適当かは要件によると思いますが、今回は前回と比較して次のような違いがあります。

- OAuth認証画面は出てこない
- トークン期限切れの考慮が不要
- メール送信元アカウントはGSuite上のアカウントでなければならない

なお今回試した内容の [公式ガイドはこのへん](https://developers.google.com/identity/protocols/OAuth2ServiceAccount?hl=ja) とかにあります・・
ちょっとでも指定を間違うと実行時に原因不明のエラーが出て辛い。。

# 修正の概要
前回作成したものをベースにして次の通り修正します。

(1/3) GCPのウェブUIでサービスアカウントを作成する
(2/3) GSuiteのウェブUIでサービスアカウントに権限を委譲する設定をする
(3/3) サンプルソースの認証部分を修正する

# 試してみる
そんなわけでやってみます。
### (1/3) GCPのウェブUIでサービスアカウントを作成する

サービスアカウントを作成します。
作成したら後の工程で使う次の３点も取得します。

- サービスアカウントのクライアントID(20桁ぐらいの数値)
- サービスアカウントのメール(...@....iam.gserviceaccount.comみたいなメール)
- サービスアカウントの秘密鍵ファイル(p12形式)

**サービスアカウントを作る**
![image.png](https://qiita-image-store.s3.amazonaws.com/0/26808/da7dae47-05d6-8430-0c50-cc4f11c0706d.png)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/26808/1d3a60d8-3c8c-11f5-081e-7d1683785cbb.png)

**出来たアカウントのオプションを修正する**
![image.png](https://qiita-image-store.s3.amazonaws.com/0/26808/9d7016c8-7117-b1f0-18ef-7178dde39629.png)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/26808/96b03358-b435-9248-061f-a82d4a93d1d4.png)

**サービスアカウントのクライアントIDとメールを取得する**
![image.png](https://qiita-image-store.s3.amazonaws.com/0/26808/ae69e26d-fa36-55a1-5b69-6b18d2cff5a9.png)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/26808/d850c738-66ba-9ed0-2dfc-ffb342c04804.png)

**秘密鍵を取得する**
![image.png](https://qiita-image-store.s3.amazonaws.com/0/26808/a586435a-b8f9-d039-2c82-b773292233a9.png)

### (2/3) GSuiteのウェブUIでサービスアカウントに権限を委譲する設定をする

![image.png](https://qiita-image-store.s3.amazonaws.com/0/26808/fb99e535-d1e0-f661-765e-c01d0ee9024c.png)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/26808/9a7733a5-b676-4027-f29f-9ffbdce90514.png)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/26808/cec234ac-9a9f-4885-d044-04bb867dd860.png)

Gmail APIのスコープは [こちら](https://developers.google.com/gmail/api/auth/scopes) に記載されています。
今回はメール送信のみなので `https://www.googleapis.com/auth/gmail.send` を指定します。
なお、ここで指定したスコープと認証処理で指定するスコープは **完全に一致** している必要があるようです。

またメール送信元として使用するアカウントのメール（GSuiteログイン時に指定するメール）も控えておきます。


### (3/3) サンプルソースの認証部分を修正する

前回のサンプルソース全体は [こちら](https://github.com/tnagao3000/sending-mail-using-gmail-api) にあります。

上記を取得した後、SendingMailUsingGmailApi.java を下記に差し替え、
さらにコメントを参考にサービスアカウントのメールほかを入れます。

```java:SendingMailUsingGmailApi.java
package com.example;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Properties;

import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.Base64;
import com.google.api.services.gmail.Gmail;
import com.google.api.services.gmail.GmailScopes;
import com.google.api.services.gmail.model.Message;

public class SendingMailUsingGmailApi {

    private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();

    private static final String SERVICE_ACCOUNT_EMAIL = "[(1/3)のサービスアカウントのメール]";        // サービスアカウントのメール
    private static final String SERVICE_ACCOUNT_P12_FILE_PATH = "[(1/3)の秘密鍵のフルパス]";         // サービスアカウントの秘密鍵(p12形式)
    private static final List<String> SCOPES = Collections.singletonList(GmailScopes.GMAIL_SEND);  // スコープ (2/3)での指定と一致している必要あり
    private static final String GSUITE_USER_EMAIL = "[(2/3)の送信元アカウントのメール]";              // GSuite上のメール送信元アカウント

    public static void main(String... args) {

        try {
            final NetHttpTransport TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();

            GoogleCredential credential = new GoogleCredential.Builder().setTransport(TRANSPORT)
                    .setJsonFactory(JSON_FACTORY)
                    .setServiceAccountId(SERVICE_ACCOUNT_EMAIL)
                    .setServiceAccountPrivateKeyFromP12File(new File(SERVICE_ACCOUNT_P12_FILE_PATH))
                    .setServiceAccountScopes(SCOPES)
                    .setServiceAccountUser(GSUITE_USER_EMAIL)
                    .build();

            Gmail service = new Gmail.Builder(TRANSPORT, JSON_FACTORY, credential).setApplicationName("demo").build();

            // (b-3) メール作成
            String to = "[ここに送信先メールアドレス]"; // FIXME
            MimeMessage mimeMessage = createEmail(to, null, "送ってみるテスト", "こいつ…動くぞ!");

            // (b-4) メール送信
            sendMessage(service, "me", mimeMessage);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * メール作成と送信
     *
     * これ以降はほぼ次の公式サンプルのまま
     * https://developers.google.com/gmail/api/guides/sending
     */
    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);

//        InternetAddress fromAddress = new InternetAddress(from);
//        email.setFrom(fromAddress);
//        email.setReplyTo(new InternetAddress[] { fromAddress });

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

    public static Message createMessageWithEmail(MimeMessage emailContent) throws MessagingException, IOException {

        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        emailContent.writeTo(buffer);
        byte[] bytes = buffer.toByteArray();
        String encodedEmail = Base64.encodeBase64URLSafeString(bytes);
        Message message = new Message();
        message.setRaw(encodedEmail);
        return message;
    }

    public static Message sendMessage(Gmail service, String userId, MimeMessage emailContent)
            throws MessagingException, IOException {

        Message message = createMessageWithEmail(emailContent);
        message = service.users().messages().send(userId, message).execute();

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

```

これにて修正は完了です。
あとは適当な方法で実行してみてください。

以上です。