【JavaFX】WebViewを使用してGoogle APIのOAuth同意画面を表示する方法
JavaFX の WebView を使用して Google API の OAuth 同意画面を表示する方法をまとめました。
1. 動作環境
- macOS 14.5
- Java 21.0.3
- JavaFX 21.0.3
- google-api-client 2.6.0
- google-oauth-client-jetty 1.36.0
- google-api-services-drive v3-rev20240628-2.0.0
2. サンプルプロジェクト
GitHub でサンプルプロジェクトを公開しています。
サンプルプロジェクトの主なファイル構成は以下の通りです。
ファイル名 | 概要 |
---|---|
App.java |
JavaFX メインクラス |
CustomBrowser.java |
カスタムブラウザクラス |
GoogleApiService.java |
Google API インターフェース |
GoogleApiServiceImpl.java |
GoogleApiService.java の実装クラス |
JavaFXGoogleApiMainController.java |
JavaFXGoogleApiMain.fxml のコントローラークラス |
OAuthConsentController.java |
OAuthConsent.fxml のコントローラークラス |
JavaFXGoogleApiMain.fxml |
メインウィンドウの FXML ファイル |
OAuthConsent.fxml |
OAuth 同意画面ウィンドウの FXML ファイル |
error.html |
OAuth エラー時のランディングページ |
success.html |
OAuth 正常時のランディングページ |
3. 事前準備
事前準備として Google Drive API クイックスタートにしたがって 認証情報をダウンロードし src/main/resources
に格納します。ファイル名はcredentials.json
とします。
4. プロジェクト概要
Google Drive のファイル一覧を表示する際の OAuth 同意画面を JavaFX の WebView を使用して表示します。
ベースとした Google Drive API クイックスタートのサンプルコードに対する主な変更点は以下の通りです。
- OAuth 同意画面ウィンドウの作成
-
AuthorizationCodeInstalledApp.Browser
を実装したカスタムブラウザクラスの作成 -
AuthorizationCodeInstalledApp
へのカスタムブラウザの設定 - カスタムブラウザが閉じられた場合に発生する
NullPointerException
の対処 -
LocalServerReceiver
のランディングページ[1]の指定
4.1. OAuth 同意画面ウィンドウの作成
OAuth 同意画面ウィンドウの FXML(OAuthConsent.fxml
)とコントローラークラス(OAuthConsentController.java
)を作成します。コントローラークラスには WebView
の getter メソッドを用意します。
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.web.WebView?>
<VBox alignment="CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="800.0" prefWidth="800.0" spacing="10.0" xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.javafxgoogleapi.OAuthConsentController">
<children>
<WebView fx:id="fxWebView" prefHeight="200.0" prefWidth="200.0" VBox.vgrow="ALWAYS" />
<Button fx:id="fxCloseButton" mnemonicParsing="false" text="閉じる" />
</children>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
</VBox>
package org.javafxgoogleapi;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class OAuthConsentController {
@FXML
private WebView fxWebView;
@FXML
private Button fxCloseButton;
public void initialize() {
/*
* 閉じるボタンのアクションを設定
*/
fxCloseButton.setOnAction(event -> {
/*
* ウィンドウを閉じる
*/
Node source = (Node) event.getSource();
Stage stage = (Stage) source.getScene().getWindow();
stage.close();
});
}
/*
* WebViewのgetter
*/
WebView getFxWebView() {
return this.fxWebView;
}
}
AuthorizationCodeInstalledApp.Browser
を実装したカスタムブラウザクラスの作成
4.2. コンストラクターには OAuth 同意画面ウィンドウの WebView
と Google API の認証コードの受信に使用するLocalServerReceiver
を渡します。LocalServerReceiver
はローカルホスト上にサーバーを起動して認証コードの受信を待ちますが、受信が行われないとサーバーが起動したままになってしまうため OAuth 同意画面ウィンドウが閉じられた場合にはそのサーバーを停止させます。
package org.javafxgoogleapi;
import java.io.IOException;
import java.io.UncheckedIOException;
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
/*
* カスタムブラウザクラス
*/
class CustomBrowser implements AuthorizationCodeInstalledApp.Browser {
private final WebView webView;
private final Stage stage;
CustomBrowser(WebView webView, LocalServerReceiver localServerReceiver) {
this.webView = webView;
this.stage = (Stage) webView.getScene().getWindow();
this.stage.setOnHidden(event -> {
/*
* ウィンドウが閉じられた場合にLocalServerReceiverをstopする
*/
try {
localServerReceiver.stop();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}
@Override
public void browse(String url) throws IOException {
/*
* WebViewでブラウズする
*/
webView.getEngine().load(url);
stage.showAndWait();
}
boolean isShowing() {
/*
* ウィンドウを表示しているか否かを返す
*/
return stage.isShowing();
}
}
AuthorizationCodeInstalledApp
へのカスタムブラウザの設定
4.3. AuthorizationCodeInstalledApp
のコンストラクターの引数にカスタムブラウザを指定します。
CustomBrowser customBrowser = new CustomBrowser(webView, localServerReceiver);
AuthorizationCodeInstalledApp authorizationCodeInstalledApp = new AuthorizationCodeInstalledApp(
googleAuthorizationCodeFlow, localServerReceiver,
customBrowser); // カスタムブラウザの指定
NullPointerException
の対処
4.4. カスタムブラウザが閉じられた場合に発生する OAuth 同意画面ウィンドウが閉じられて LocalServerReceiver
が停止すると認証コードが null になるためAuthorizationCodeInstalledApp
のauthorize
メソッドでNullPointerException
が発生しますがその場合は正常系として処理します。
Credential credential;
try {
credential = authorizationCodeInstalledApp.authorize("user");
} catch (NullPointerException e) {
if (!customBrowser.isShowing()) {
/*
* カスタムブラウザが閉じられた場合の処理
*/
return null;
}
throw e;
}
LocalServerReceiver
のランディングページの指定
4.5. デフォルトでは LocalServerReceiver
が内部で起動したサーバーが HTTP レスポンスボディ(OutputStreamWriter
)にランディングページを書き込んでいますが、 WebView
ではその表示がうまくいかなかったので正常終了時(success.html
)とエラー時(error.html
)のランディングページを作成してLocalServerReceiver
に指定します。
LocalServerReceiver localServerReceiver = new LocalServerReceiver.Builder()
.setPort(-1) // 未使用のポートを使う
.setLandingPages(
getClass().getResource("/success.html").toString(), // OAuth 2.0認証成功時の表示ページ
getClass().getResource("/error.html").toString()) // OAuth 2.0認証失敗時の表示ページ
.build();
正常時とエラー時のランディングページの例は以下の通りです。
<!DOCTYPE html>
<html lang="ja">
<head>
<title>OAuth 2.0 Authentication</title>
<meta charset="UTF-8">
</head>
<body>
<h2>認証コードを受け取りました。このウィンドウを閉じてください。</h2>
</body>
</html>
<!DOCTYPE html>
<html lang="ja">
<head>
<title>OAuth 2.0 Authentication</title>
<meta charset="UTF-8">
</head>
<body>
<h2>認証がエラーになりました。</h2>
</body>
</html>
-
認証プロセス完了後にウィンドウに表示するページ ↩︎
Discussion