afnf.net

STARTTLSからGmail APIへの移行

移行の経緯

このブログエンジンblog-java2では、新しいコメントが投稿されると、私のGmailアドレス宛にメールが送信されるようになっています。

今までこのメール送信にはSTARTTLSを使っていたのですが、生のパスワードが必要なため、Googleアカウントの「安全性の低いアプリの許可」を有効にする必要がありました。

またこれを許可したとしても、例えばVPSなど、普段と異なるIPアドレス帯から認証しようとすると、アクセスがブロックされることがありました。

そもそも、(難読化されているとはいえ)サーバにGoogleアカウントの生パスワードが保存されているのは宜しくありません。

Gmail APIによるOAuth2.0認証に移行することで、この問題を解決します。基本的には公式の手順に従うだけなのですが、一般的なOAuthアプリケーションと異なり特定のアカウントからメール送信するだけなので、もっとシンプルになります。

Googleアカウント設定とJSON取得

以下の準備が必要になります。

  1. このウィザードに従って、Gmail APIを有効化 gmail01 gmail02

  2. 「APIを呼び出す場所」で「その他UI」を選択 gmail05
    • 今回は1つの固定アカウントにアクセスできれば良いので「その他UI」を選択します
    • 一般的なOAuth利用アプリのように、エンドユーザのアカウントにアクセスする場合は「ウェブサーバ」を選択する必要があります

  3. OAuth 2.0 クライアントIDの入力 gmail06

  4. 認証情報のJSONをダウンロード gmail07

このJSONに、クライアントIDやクライアントシークレットなどが含まれています。client_secret.jsonとしてローカルに保存しておきます。

Gmail API

情報にアクセスするためにはアクセストークンが必要ですが、セキュリティの制約上、有効期限が短く設定されています。初回認証時に取得できるリフレッシュトークンを使って、必要に応じてアクセストークンを更新しなければなりません。

Gmail APIとclient_secret.jsonを使うと、上記のような面倒さを隠蔽してくれます。

https://developers.google.com/gmail/api/quickstart/java

上記の公式サンプルからエッセンスを抽出し、SmtpManager.javaを作成しました。

private static Gmail getGmailService(String basedir, String appName) throws Exception {

  // 機密情報ファイルのパス
  File DATA_STORE_DIR = new java.io.File(basedir, "gmail-secrets");
  File SECRET_JSON = new java.io.File(DATA_STORE_DIR, "client_secret.json");

  // 準備
  FileDataStoreFactory DATA_STORE_FACTORY = new FileDataStoreFactory(DATA_STORE_DIR);
  JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
  HttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();

  // 送信のみ
  List<String> SCOPES = Arrays.asList(GmailScopes.GMAIL_SEND);

  // Credential取得
  try (InputStream in = FileUtils.openInputStream(SECRET_JSON)) {
    GoogleClientSecrets clientSecrets 
      = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in));
    GoogleAuthorizationCodeFlow flow
      = new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT, 
            JSON_FACTORY, clientSecrets, SCOPES).setDataStoreFactory(
            DATA_STORE_FACTORY).setAccessType("offline").build();
    Credential credential = new AuthorizationCodeInstalledApp(
           flow, new LocalServerReceiver()).authorize("user");

    // Gmailインスタンス生成
    return new Gmail.Builder(HTTP_TRANSPORT, JSON_FACTORY, 
                 credential).setApplicationName(appName).build();
  }
}

public static void main(String[] args) throws Throwable {
  Gmail gmail = getGmailService("target", "test");
  System.out.println(gmail.toString());
}

以下の手順により、アプリで使用する認証情報を取得できます。

  1. APIコンソールからダウンロードしたclient_secret.jsonを、target/gmail-secretsフォルダに保存
  2. mainクラスを呼び出す
  3. 自動的にWebブラウザが開かれ、Google認証を求められるので許可する
  4. target/gmail-secretsフォルダにStoredCredentialというファイルが保存される

メール送信

この2ファイルを/home/username/gmail-secrets/にコピーすれば、アプリでは以下のコードでメール送信できます。

Gmail service = getGmailService(System.getProperty("user.home"), title);
service.users().messages().send("me", gmailmsg).execute();

繰り返しになりますが、上記は特定のアカウントだけにアクセスする場合の手順です。

一般のOAuthアプリであれば、ユーザ毎にリフレッシュトークンの保存が必要になるはずです。

comments (0)

blog-java2 engine (build:2019-02-23 17:57 JST)