Edited at

OAuth 2.0 認証クライアントの簡単な実装例(何故かレシピ風)

More than 3 years have passed since last update.


今日の料理

19日の記事「OAuth 2.0 認証のクライアントを作る時の注意点」では、クライアント作る時の注意点を紹介しました。今回はその実践篇として、簡単なサンプルプログラムを紹介したいと思います。

というわけで、今日は Facebook にログインして、自分の誕生日を抽出するためのアクセストークンを取得する簡単な Java プログラムを作ります。


材料


  • 言語環境


    • Java SE 8u66



  • 追加ライブラリ


    • Apache Oltu 1.0.1



  • サービス


    • Facebook



  • 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_urihttps://www.facebook.com/connect/login_success.html に指定する必要があります。

さらに、デスクトップアプリの場合、response_typetoken に指定しなければなりません。その影響で認証フローが変わるが詳細はあとで説明します。


リフレッシュトークンのサポート有無と有効期限

Facebook はリフレッシュトークンをサポートしません。その代わりにアクセストークンの有効期間を延長するフローと、アクセストークンの有効性を検証するツール5があります。


作り方


1)アプリ情報の登録

Facebook の開発者向けサイト facebook for developers に新規アプリを追加します。

デスクトップアプリに対応するプラットフォームはないので、advance setup で作成してから、アプリのサイドメニューから設定(英語表示時はSettings)を開いて以下の項目を設定しておきます。


  • Advanced の内容

    Advenced の設定


  • AdvancedClient OAuth Settings の内容

    クライアントの OAuth 設定


アプリの Dashboard の App ID は接続時必要になるのでメモしておきます6

キャプチャ3.PNG


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();
}

そして ControllerWebView 追加します。

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}&amp;redirect_uri={redirect-uri}

の形でエンドポイントを使用します。その他必要なパラメータ scoperesponse_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_typetoken に指定する影響で、ユーザがログインしたあと、デスクトップアプリに返すのは認可コードではなくアクセストークンになります。

実際の返す方は、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("ここでは考慮しない");
}
}
}


できあがり

実際表示されたログイン画面です。

ログイン画面

ログインしたあと、情報へのアクセスの許可が求められます。画面が小さい?そんなこと知りません~

キャプチャ.PNG

実行した IntelliJ IDEA のコンソールにアクセストークンと有効期間が出力されます。

console.PNG


試食

取得したトークンを Graph API Explorer で試します。

アプリケーション、Access Token、使用 API と結果フィールドを指定して、Submit を押してたら、

apiexp.PNG

誕生日の日付を抽出できました。なんと、抽出した日付が今日と一緒ですね!誕生日おめでとう!





  1. Facebook のドキュメントに Atlas API と Marketing API があるが、アーキテクチャ上は Graph API の一部ようです。 



  2. ユーザ公開プロフィールにアクセスするための public_profile は自動的に付与されます。 



  3. Manually Build a Login Flow です。ドキュメントは英語しか無いみたいです。 



  4. Facebook Login for Apps のことです。 



  5. Access Token Debugger のことです。 



  6. 今回のサンプルは使用する認証フローの関係上 App Secret を使用していません。