OAuth 2.0を使用して GmailのSMTPでメール送信したい

前提条件

Gmailのアカウントを持っていること。

Google Cloudの設定

https://console.cloud.google.com/にアクセスして、プロジェクトを作成する。

わかりやすい名前を付ける。

User Typeは「外部」を選択する。

テスト用なのでアプリ情報は適当に入力する。

プロジェクトを作成したら、「認証情報」タブの「認証情報を作成」をクリックする。

「OAuthクライアントID」を選択する。

アプリケーションの種類には「デスクトップアプリ」を選択する。メールを送信するだけなので、ライブラリからGmail APIを追加する必要はない。

作成が完了したら、クライアント ID、クライアント シークレットが発行されるので、メモしておく。

プログラムの例

build.gradle
plugins {
	id 'java'
}

group = 'oauth2gmail'
version = '0.0.1-SNAPSHOT'

java {
	sourceCompatibility = '17'
}

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
    implementation 'com.google.api-client:google-api-client:2.2.0'
    implementation 'com.sun.mail:jakarta.mail:2.0.1'
}

tasks.named('test') {
	useJUnitPlatform()
}
GmailOAuth.java

メモしておいたクライアント ID、クライアント シークレットを、CLIENT_ID、CLIENT_SECRET変数にそれぞれ設定する。

今回リダイレクト処理は必要ないので、リダイレクトURLにはurn:ietf:wg:oauth:2.0:oobを設定する。これを設定するとリダイレクトせずに直接画面に認証コードが表示される。

MAIL_FROMにGmailのアドレスを設定する。MAIL_TOにはテストメールを送っても大丈夫なメールアドレスを設定する。

public class GmailOAuth {

    private static final String SCOPE = "https://mail.google.com/";
    private static final String CLIENT_ID = "CLIENT_ID";
    private static final String CLIENT_SECRET = "CLIENT_SECRET";
    private static final String REDIRECT_URI = "urn:ietf:wg:oauth:2.0:oob";
    private static final String MAIL_FROM = "sender@foo.bar";
    private static final String MAIL_TO = "receiver@foo.bar";

    public static void main(String[] args) throws IOException, MessagingException {
        // 取得した認証コードを保存するフォルダを指定
        var fileDataStoreFactory = new FileDataStoreFactory(new File(System.getProperty("java.io.tmpdir")));

        // OAuth 2.0 の認証フローを作成
        var flow = new GoogleAuthorizationCodeFlow.Builder(new NetHttpTransport(),
                GsonFactory.getDefaultInstance(), CLIENT_ID, CLIENT_SECRET, Set.of(SCOPE))
                        .setCredentialDataStore(StoredCredential.getDefaultDataStore(fileDataStoreFactory)).build();

        // アクセス トークンを取得
        var credential = flow.loadCredential("user");

        // 取得済みの認証情報があるか、またそれは有効か確認
        if (credential == null || (credential.getExpiresInSeconds() < 100 && !credential.refreshToken())) {
            // 有効なコードがなかった場合、新たに認可コードを取得
            var url = flow.newAuthorizationUrl().setRedirectUri(REDIRECT_URI).build();
            System.out.println("Please open the following URL in your browser then type the authorization code:");
            System.out.println("  " + url);
            System.out.println("Please enter your authentication code:");

            try (var s = new Scanner(System.in)) {
                var code = s.nextLine();
                var tokenResponse = flow.newTokenRequest(code).setRedirectUri(REDIRECT_URI).execute();
                credential = flow.createAndStoreCredential(tokenResponse, "user");
            }
        }

        // SMTP セッションを作成
        var props = new Properties();
        props.put("mail.smtp.host", "smtp.gmail.com");
        props.put("mail.smtp.port", "587");
        props.put("mail.smtp.auth", "true");
        props.put("mail.smtp.auth.mechanisms", "XOAUTH2");
        props.put("mail.smtp.starttls.enable", "true");
        var session = Session.getDefaultInstance(props);

        // メールを作成
        var message = new MimeMessage(session);
        message.setFrom(new InternetAddress(MAIL_FROM));
        message.setRecipient(Message.RecipientType.TO, new InternetAddress(MAIL_TO));
        message.setSubject("Gmail から送信したメール");
        message.setText("このメールは、Gmail の SMTP で OAuth 2.0 認証を使って送信しました。");

        // メールを送信
        var transport = session.getTransport("smtp");
        transport.connect(MAIL_FROM, credential.getAccessToken());
        transport.sendMessage(message, message.getAllRecipients());
        transport.close();

        System.out.println("メールを送信しました。");
    }
}

実行

初回実行時、下記のメッセージが表示されるようになっている。表示されているURLにブラウザでアクセスし、アクセス権を付与する。

Please open the following URL in your browser then type the authorization code:
  ここに表示されるURLにブラウザでアクセスする
Please enter your authentication code:

ChromeブラウザでGmailにログイン済みの状態であれば、下記の通りアカウントの選択に表示される。もちろん別のアカウントを選択しても良い。

テスト用のプロジェクトのため、注意事項が表示されるがそのまま続行する。

アクセス権の付与が間違いないことを確認して、続行する。

表示された認証コードをコピーして、「Please enter your authentication code:」の下に貼り付けEnterを押下する。

「メールを送信しました。」と表示されたら、送信先のメールアドレスにメールが送信されたことを確認する。

2回目以降は、認証コードの有効期限内であれば、上記の手順無しでメール送信できる。逆にもう一度最初から実行したい場合は、「System.getProperty(“java.io.tmpdir”)」フォルダ内の「StoredCredential」というファイルを削除する。