今日の料理
19日の記事「OAuth 2.0 認証のクライアントを作る時の注意点」では、クライアント作る時の注意点を紹介しました。今回はその実践篇として、簡単なサンプルプログラムを紹介したいと思います。
というわけで、今日は Facebook にログインして、自分の誕生日を抽出するためのアクセストークンを取得する簡単な Java プログラムを作ります。
材料
- 言語環境
- Java SE 8u66
- 追加ライブラリ
- Apache Oltu 1.0.1
- サービス
- IDE
- IntelliJ IDEA 15.0.2
下準備
料理の前に、味付けのポイント(前回の話に出た注意点)を予めを把握しておきましょう。
クライアントの処理対象リソースと操作内容
プログラムのリソース処理をユーザの誕生日情報取得だけなので、ここでは Facebook の Graph API を使用します1。実際必要となる scope は user_birthday
になります2。
認可コードリダイレクト先の指定制限
Facebook Login のドキュメント3によると、SDK を使用していないネイティブデスクトップアプリにログインが必要な場合、https://www.facebook.com/dialog/oauth?client_id={app-id}&redirect_uri={redirect-uri}
の形でエンドポイントを指定することで、Facebook の用意したログイン画面4を使用できます。
ただし、この場合の redirect_uri は https://www.facebook.com/connect/login_success.html
に指定する必要があります。
さらに、デスクトップアプリの場合、response_type は token
に指定しなければなりません。その影響で認証フローが変わるが詳細はあとで説明します。
リフレッシュトークンのサポート有無と有効期限
Facebook はリフレッシュトークンをサポートしません。その代わりにアクセストークンの有効期間を延長するフローと、アクセストークンの有効性を検証するツール5があります。
作り方
1)アプリ情報の登録
Facebook の開発者向けサイト facebook for developers に新規アプリを追加します。
デスクトップアプリに対応するプラットフォームはないので、advance setup で作成してから、アプリのサイドメニューから設定(英語表示時はSettings)を開いて以下の項目を設定しておきます。
- Advanced の内容
- Advanced > Client OAuth Settings の内容
アプリの Dashboard の App ID は接続時必要になるのでメモしておきます6。
2)Web ブラウザの準備
ログイン画面を出すには Web ブラウザが必要ですが、ここでは JavaFX の WebView
を使用します。手間を省けるため、IntelliJ IDEA のプロジェクトテンプレート「JavaFX Application」そのまま使います。
デフォルトの構成をそのまま使います。プロジェクトはこんな感じです。
ただ、デフォルトのウィンドウサイズが小さくて不便なので、Main
を少し変えます。
@Override
public void start(Stage primaryStage) throws Exception{
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
primaryStage.setTitle("Hello OAuth"); // World じゃないよ
primaryStage.setScene(new Scene(root)); // サイズはお任せで
primaryStage.show();
}
そして Controller
にWebView
追加します。
public class Controller {
@FXML
private WebView webView;
@FXML
private void initialize() {
WebEngine engine = webView.getEngine();
engine.getLoadWorker().stateProperty().addListener(newChangeListener());
engine.load(buildLoginUrlString());
}
private ChangeListener<State> newChangeListener() { ... } // あとで埋める
private String buildLoginUrlString() { ... } // あとで埋める
}
sample.fxml
のバインディングはこんな感じで、WebView
さえあれば十分です。サイズもここで入れておきます。
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.web.*?>
<?import javafx.scene.layout.*?>
<GridPane alignment="center" hgap="10" vgap="10" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8" fx:controller="sample.Controller">
<children>
<WebView prefWidth="1024" prefHeight="768" fx:id="webView"/>
</children>
</GridPane>
3)ログイン用URL文字列の作成
上に説明したように、ログイン画面を出すために https://www.facebook.com/dialog/oauth?client_id={app-id}&redirect_uri={redirect-uri}
の形でエンドポイントを使用します。その他必要なパラメータ scope と response_type も同じクエリに入れます。
リダイレクト先のURLをエンコードする必要があるので、ここでは自前で作らず、ライブラリに任せます。
private String buildLoginUrlString() {
try {
return OAuthClientRequest
.authorizationLocation(FACEBOOK_OAUTH_LOGIN_DIALOG)
.setClientId(CLIENT_ID)
.setRedirectURI(REDIRECT_URI)
.setResponseType("token")
.setScope("user_birthday")
.buildQueryMessage()
.getLocationUri();
} catch (OAuthSystemException e) {
throw new RuntimeException("ここでは考慮しない");
}
}
4) アクセストークン取得
えっ、いきなりアクセストークン?認可コードもなしで?
ええ、その通りです。
上の説明に出た response_type を token
に指定する影響で、ユーザがログインしたあと、デスクトップアプリに返すのは認可コードではなくアクセストークンになります。
実際の返す方は、redirect_uri で指定したページにリダイレクトする際に、URLに付け加える形になります。形式は YOUR_REDIRECT_URI#access_token={access_token}&expires_in={expires_in}
です。
WebView
を初期化する際に、ChangeListener
を指定するのはこの処理を実現するためです。その ChangeListener
の実装はこんな感じです。
private ChangeListener<State> newChangeListener() {
return (value, oldState, newState) -> {
if (newState == State.SUCCEEDED) {
handleUrlString(webView.getEngine().getLocation());
}
};
}
handleUrlString
は文字列解析のメソッドです。
private void handleUrlString(String urlString) {
if (urlString.startsWith(REDIRECT_URI)) {
// クエリから詳細を抽出
String queryStr = urlString.replaceFirst(REDIRECT_URI + "#", "");
Map<String, String> queryDetail = Arrays.stream(queryStr.split("&")).collect(
Collectors.toMap(
(String s) -> s.substring(0, s.indexOf("=")),
(String s) -> s.substring(s.indexOf("=") + 1)
));
if (queryDetail.containsKey("access_token")) {
String token = queryDetail.get("access_token");
String expire = queryDetail.get("expires_in");
System.out.println("access_token:\n" + token);
System.out.println("expires_in: (sec)\n" + expire);
} else {
throw new IllegalStateException("ここでは考慮しない");
}
}
}
できあがり
ログインしたあと、情報へのアクセスの許可が求められます。画面が小さい?そんなこと知りません~
実行した IntelliJ IDEA のコンソールにアクセストークンと有効期間が出力されます。
試食
取得したトークンを Graph API Explorer で試します。
アプリケーション、Access Token、使用 API と結果フィールドを指定して、Submit を押してたら、
誕生日の日付を抽出できました。なんと、抽出した日付が今日と一緒ですね!誕生日おめでとう!
-
Facebook のドキュメントに Atlas API と Marketing API があるが、アーキテクチャ上は Graph API の一部ようです。 ↩
-
ユーザ公開プロフィールにアクセスするための
public_profile
は自動的に付与されます。 ↩ -
Manually Build a Login Flow です。ドキュメントは英語しか無いみたいです。 ↩
-
Facebook Login for Apps のことです。 ↩
-
Access Token Debugger のことです。 ↩
-
今回のサンプルは使用する認証フローの関係上 App Secret を使用していません。 ↩