このページは Authlete 2.x 用です。3.0 では サンプル認可サーバーのセットアップ(3.0) をご覧ください。
はじめに
この短いチュートリアルでは、Java ウェブアプリケーションを Authlete API と統合する手順を説明します。シナリオ
AuthleticGearという実店舗を持つ小売業者は、顧客向けにロイヤルティプログラムを運営しています。プログラムのメンバーは、ロイヤルティプログラムのウェブサイトにログインして、ポイント残高や取引を確認したり、リンクされた銀行口座への現金振込にポイントを利用したりできます。ロイヤルティプログラムのウェブサイトは、Eclipse Jerseyを介してRESTful API を提供するJavaウェブアプリケーションバックエンドと、HTML5/JavaScriptフロントエンドで構成されています。


- 認可の開始
- 顧客がeコマースサイトで「Link Account」をクリックします。
- eコマースのOAuthクライアントが、クライアントIDとリダイレクトURLをクエリパラメータとして含む形で、顧客のブラウザをロイヤルティプログラムの認可サーバーにリダイレクトします。
- ロイヤルティプログラムの認可サーバーがログインフォームを表示します。
- 顧客が通常の方法でロイヤルティプログラムのウェブサイトにログインします。
- クライアントへの認可コードの発行
- 認可サーバーが顧客のブラウザをeコマースサイトにリダイレクトし、認可コードを渡します。
- クライアントのアクセストークン要求の処理
- eコマースのOAuthクライアントが認可コード、クライアントID、およびクライアントシークレットを認可サーバーに送信します。
- 認可サーバーがアクセストークンをクライアントに返します。
- クライアントからのロイヤルティプログラムAPIへのリクエストの検証
- eコマースウェブサイトは、顧客のポイント残高を要求するAPIリクエストにアクセストークンを含めます。
- ロイヤルティプログラムのリソースサーバーはアクセストークンを検証し、そのリクエストに顧客の識別子を添付して、ロイヤルティプログラムAPIに処理を移します。
前提条件
必要なもの:- Docker Desktop
- 任意のコードエディタ
- Java EE の基礎知識
セットアップ
デモシステムは、eコマースウェブサイト用とロイヤルティサイト用の2つのJava EEウェブアプリケーションをそれぞれ格納するDockerコンテナのペアとして実装されています。Dockerネットワークの作成
上記のフローのステップ3では、eコマースOAuthクライアントがロイヤルティ認可サーバーに直接リクエストを送信します。このため、eコマースコンテナがロイヤルティコンテナのIPアドレスを解決できるように、Dockerネットワークを作成する必要があります。以下のコマンドを実行してください:Start the E-Commerce Container
Start the e-commerce container withdocker runの引数を変更できます:
- Tomcatはコンテナ内でポート8080でリッスンするように設定されています。上記の例では、Dockerがホストのポート8080にそのポートを公開しています。マシン上でポート8080がすでに使用されている場合は、
--publish 12345:8080を指定して別のホストポートを選択できます。その場合、チュートリアル全体で8080を選択したポートに変更する必要があります。
ロイヤルティコンテナの起動
ロイヤルティコンテナは、Docker bind mountを使用してローカルディレクトリにソースコードを公開します。これにより、任意のソースコードエディターやIDEでホストマシン(Dockerホスト)のコードを編集できます。 ソースディレクトリは、ローカルマシン上の任意の場所に作成できます。このチュートリアルでは、このディレクトリを$SOURCE_ROOTと呼びます。
ソースディレクトリはDockerコンテナを起動する前に存在している必要があり、docker runでコンテナを起動する際に--mountオプションで参照する必要があります。
例えば、/Users/jdoe/authlete_srcをソースディレクトリとして使用する場合:
ls /Users/jdoe/authlete_srcコマンドでソースディレクトリを見ると、loyaltyサブディレクトリが含まれていることが確認できます。このディレクトリにはロイヤルティウェブアプリケーションのソースコードが含まれています。このソースディレクトリにはgitリポジトリも含まれているため、チュートリアルの各ステップ後に必要に応じてコードをチェックアウトすることが簡単にできます。
ロイヤルティコンテナは、eコマースコンテナとは異なるホストポートでリッスンする必要があります。このチュートリアルでは、eコマースコンテナはポート8080で、ロイヤルティコンテナはポート8081でリッスンします。
前セクションに記載されているように、docker runの引数を変更すると、ホストポート番号を変更することができます。
eコマースコンテナと同様のコマンドを使用して、コンテナログの確認、コンテナの一時停止、および再開を行います。その際は、eコマースインスタンス名の代わりにロイヤルティインスタンス名であるauthlete-loyaltyを使用することを忘れないでください。
サンプルウェブサイトの確認
ロイヤルティプログラムウェブサイト
http://localhost:8081/loyalty/にアクセスします。ロイヤルティプログラムのホームページが表示され、プレースホルダーテキストとログインリンクが表示されます。リンクをクリックして、ページに表示されているクレデンシャルのセットのいずれか1つを使ってログインします。すると、アカウントの概要が表示され、取引リストが表示されます。ロイヤルティプログラムウェブアプリケーションは、アプリケーションが起動するたびにサンプル取引が読み込まれるインメモリデータベースを使用しています。「Redeem Points」をクリックして、リンクされた銀行口座に対して現金を振り込むためにロイヤルティポイントを交換するシミュレーションを行うことができます。この機能を使用すると、システムが稼働している間にアカウント残高を簡単に変更できるため、残高が動的に取得されていることを確認できます。
eコマースウェブサイト
ブラウザでhttp://localhost:8080/ecommerce/を開きます。このページは、典型的なeコマースウェブサイトのシンプルなモックアップです。機能している唯一の機能は、「Link my Loyalty account」リンクです。リンクをクリックすると、ロイヤルティサイトにログインしていない場合は、ログインするためにロイヤルティサイトに誘導されます。すでにログインしている場合、またはログイン後は、http://localhost:8081/loyalty/oauth/authorizationで404エラーが表示されます。これは、ロイヤルティプログラムがまだOAuth 2.0をサポートしていないためです。

- Eclipse Jersey Java RESTフレームワーク、バージョン2.34
- Hibernate Java Persistence API (JPA) の実装、バージョン5.6.4.Final
- H2 インメモリデータベースエンジン、バージョン2.1.210
- Apache Log4j ロギングフレームワーク、バージョン2.17.1
- Java Server Pages (JSP) をeコマースウェブサイトで使用
- HTML5、JavaScript、CSSをロイヤルティウェブサイトで使用
Authleteアカウントの作成とクライアントアプリケーションの設定
Authleteアカウントにサインアップし、サービスオーナーコンソールに遷移します。すると、初期作成されたサービスの一覧が表示されます。





http://localhost:8080/ecommerce/oauth
その後、作成をクリックし、モックのリダイレクトURIを削除します。

クレデンシャルを設定しクライアントアプリケーションを再起動する
eコマースアプリケーションは環境変数からクレデンシャルを読み取るため、eコマースコンテナを停止して、先ほどメモしたクライアントIDとシークレットを渡したうえで再起動します。 まず、eコマースコンテナを停止し、削除します:CLIENT_IDとCLIENT_SECRET環境変数を設定します:
始める前に
前述の通り、ソースディレクトリにはGitリポジトリが含まれています。リポジトリが正しい開始状態にあることを確認するために、以下のコマンドを実行します:mainブランチではない場合は、以下のコマンドで正しいブランチをチェックアウトします:
認可サーブレットの作成
$SOURCE_ROOT/loyalty/src/main/java/com/authlete/simpleauthにoauthという新しいディレクトリを作成し、その中にOAuthAuthorizationServlet.javaという新しいJavaソースファイルを以下の内容で作成します:
LoginUtilsクラスのisPublicPage()メソッドは、ユーザーのログインを必要とせずに返すことができるリソースを判断します。OAuthサーブレットのパスをそのリソースリストに追加し、サーブレットがログインプロセスを制御できるようにする必要があります。
$SOURCE_ROOT/loyalty/src/main/java/com/authlete/simpleauth/LoginUtils.javaを開き、isPublicPage()メソッドを以下のコードに置き換えます:
AuthleteのAuthorization APIの呼び出し
先ほど作成したOAuthAuthorizationServlet.javaファイルに戻り、doGet()メソッドに以下のコードを追加します:
initiateAuthleteAuthorization()メソッドを定義します:
- サーブレットはAuthlete APIとHTTPを介してやり取りするため、Jersey HTTPクライアントを取得します。 注: Authlete Java SDKを使用することもできますが、このチュートリアルではAuthlete APIと直接やり取りする方法を示し、メッセージフローを明確に理解できるようにしています。
-
Authlete APIは、以下のようにクエリ文字列を含む
parametersプロパティをもつJSONペイロードを期待しています:
- OAuthフローを続行するために、Authleteの/auth/authorizationエンドポイントを呼び出します。
- サーブレットがAPI呼び出しを行い、レスポンスを別のJava Mapにパースします。
-
レスポンスの
actionプロパティは、サーブレットが次に何をすべきかを示し、responseContentはクライアントに返すべきデータを保持します。 -
OAuthは非常に柔軟なプロトコルであり、ユースケースやサービス設定に応じて、この時点でサービスが取るべき多くのアクションがあります。Authleteの/auth/authorizationエンドポイントのドキュメントで「Description」をクリックすると、すべてのパターンを確認できます。
ここで注目するのは
INTERACTIONです。これは、ユーザーとの何らかのインタラクションが必要であることを示しています。ユースケースを振り返ってみると、この時点で、ユーザーがロイヤルティプログラムのウェブサイトでアクティブなセッションを持っているかどうかに関係なく、ログインを促す必要があります。ここでのコードは、APIレスポンスにLOGINエントリを含むpromptsプロパティがあるかどうかを確認し、そのケースを処理します。promptsプロパティがない、または単一のLOGINエントリ以外のものが含まれている場合、実行はメソッドの最後のエラーハンドラー(9)に移ります。 - 後続処理で利用するためにAPIレスポンスをセッションに保存し、ユーザーをログインページにリダイレクトします。ログイン後、ユーザーは再びこのサーブレットにリダイレクトされます。
-
Authlete APIは、
actionプロパティを介してエラーを示す場合があり、サーブレットはそれに応じて処理する必要があります。この場合は、responseContentがエラーレスポンスのペイロードを保持しています。 -
actionが想定外の値だった場合、または他のエラーが発生した場合、一般的なエラーを返します。
$SOURCE_ROOT/loyalty/src/main/java/com/authlete/simpleauth/oauthディレクトリにOAuthUtils.javaを以下の内容で作成します:
OAuthUtils.javaの内容を見てみましょう:
getAuthleteCredential()は、先ほど作成したJSONファイルからAuthlete APIキーとシークレットを読み込みます。getClient()は、初回実行時にHTTPクライアントを作成し、Authlete APIに対するHTTPベーシック認証を使用して認証します。このクライアントは、今後の使用のためにサーブレットコンテキストに保存されます。setResponseBody()は、ステータスコードとJSONコンテンツを持つHttpServletResponseオブジェクトを設定します。prettyPrint()は、JavaのMapを整形されたJSONとして表示します。
AuthleteCredential.javaを以下の内容で作成します:
次のステップのスタブを追加する
今すぐアプリケーションを再ビルドして実行すると、eコマースサイトで「Link my Loyalty Account」をクリックしてログインすると、ログインを繰り返し求められることになります。サーブレットは、HTTPセッションにAuthlete APIのレスポンスを保存しています。次のステップを実行するためには、そのレスポンスが存在するかどうかを確認する必要があります。OAuthAuthorizationServlet.javaに戻り、doGet()関数を以下のコードで置き換えます。
OAuthAuthorizationServletクラスに追加します:
アプリケーションの再ビルド
Dockerコンテナには、アプリケーションを再ビルドし、それをTomcatに再デプロイするスクリプトが含まれています。すべての変更を保存し、ターミナルで以下のスクリプトを実行します:

変更をテストする
http://localhost:8080/ecommerceにアクセスし、「Link my Loyalty Account」をクリックすると、ログインを求められます。ログインをすると、先ほど追加した「not yet implemented」というエラーが表示されます:トラブルシューティング
何か問題が発生した場合は、ソースを元の状態に戻すか、ステップ1の終了時点のソースをチェックアウトしてください。 変更を破棄するには:ステップ2: クライアントに認可コードを発行

AuthleteのAuthorization Issue APIの呼び出し
OAuthAuthorizationServlet.javaのprocessAuthleteAuthorization()メソッドのスタブ実装を以下のコードに置き換えます:
- Authlete APIに送信するパラメーターをマップで作成します。
-
Authleteによる各認可は、最初のAuthlete API呼び出しで返される一意の
ticketによって識別されます。ticketの値は、後続のAPIリクエストに含める必要があります。 -
ユーザーが正常にログインしていることを確認します。もしもログインしていない場合は/auth/authorization/failエンドポイントを呼び出して失敗を通知します。
OAuthUtils.handleAuthleteApiCall()メソッドについては後ほど説明します。 -
認証されたユーザーのユーザー名を
subjectとしてリクエストマップに追加し、Authleteの/auth/authorization/issueエンドポイントを呼び出します。
scopeパラメータを渡しませんが、サードパーティが関わるOAuthインタラクションでは、クライアントは通常、scopeパラメータで権限のリストを渡し、サーブレットはクライアントがそれらの権限を受け取ることに対するエンドユーザーの同意を求めます。
認可サーブレットのリファクタリング
/auth/authorization/issueエンドポイントのドキュメントを見ると、異なる操作を行なっているものの/auth/authorizationエンドポイントと非常に似た動作をしていることがわかります。/auth/authorization/issueエンドポイントはactionとresponseContentを含むJSONオブジェクトを返し、/auth/authorizationエンドポイントと同様のactionの値を持つ可能性があります。今回は、サーブレットがLOCATIONアクションを期待しており、HTTPステータス302 Foundをブラウザに返し、クエリ文字列に認可コードを含むURLにリダイレクトするよう指示します。
initiateAuthleteAuthorization()からprocessAuthleteAuthorization()にコードをコピーする代わりに、共通コードを抽出できます。以下の静的メソッドをOAuthUtilsに貼り付けてください:
initiateAuthleteAuthorization()からのエラーハンドリングが含まれており、LOCATIONアクションの処理も追加されています。次に、OAuthUtilsの先頭に以下のスタティック変数を追加します:
OAuthAuthorizationServletに戻り、initiateAuthleteAuthorization()を以下のコードに置き換えます:
OAuthUtils.handleAuthleteApiCall()に「定型文」コードの多くを抽出しました。残ったコードは、認可の開始に関する具体的な処理に焦点を絞っています。
アプリケーションを再ビルドして変更をテストする
再度、すべての変更を保存し、再ビルドスクリプトを実行してアプリを再ビルドおよび再デプロイします:
トラブルシューティング
問題が発生した場合、変更を破棄してソースをステップ1の終了時点に戻すか、ステップ2の終了時点のソースをチェックアウトしてください。 変更を破棄するには:ステップ3: クライアントのアクセストークン要求を処理

OAuthAuthorizationServlet.javaと同じディレクトリに、新しいJavaソースファイルOAuthTokenServlet.javaを作成します。
initiateAuthleteAuthorization()メソッドと非常に似ています。主な違いは次のとおりです。
- HTTP POSTリクエストを処理する点
/auth/authorizationではなく/auth/tokenを呼び出す点INTERACTIONではなくOKアクションを処理し、HTTPステータス200 OKとresponseContentをクライアントに返す点
/auth/tokenエンドポイントはリクエストボディを期待しており、actionがOKまたは3つのエラー値のいずれかとなっているレスポンスを返すことがわかります。以前に見た2つのエラーコードに加え、INVALID_CLIENTという新しいものがあります。INVALID_CLIENTを処理するためにOAuthUtils.java内のhandleAuthleteApiCallを拡張するのは簡単です。
switch 文を次のコードに置き換えます:
INVALID_CLIENTはBAD_REQUESTと同様に処理されます。
アプリケーションの再ビルドとテスト
再ビルドスクリプトを実行してアプリを再ビルドおよび再デプロイします:
http://authlete-loyalty:8080/loyalty/api/currentCustomerを呼び出しますが、JSONレスポンスではなく、ログインページのHTMLを受け取っています。
eコマースアプリがエンドユーザーのロイヤルティプログラムデータを取得できるようにするために、最後のステップを完了する必要があります。
トラブルシューティング
問題が発生した場合、変更を破棄してソースをステップ2の終了時点に戻すか、ステップ3の終了時点のソースをチェックアウトしてください。 変更を破棄するには:ステップ4: クライアントのロイヤルティプログラムAPIへのリクエストを検証

- リクエストされたページが「公開」されている場合、リクエストを許可します。公開されているのはログインページ、フロントページ、CSSファイル、OAuthサーブレットのみです。それ以外のすべてのURLには、ユーザー認証が必要です。
- ユーザーが認証されると、ログインサーブレットはアカウントオブジェクトをHTTPセッションに添付します。フィルタは、そのセッションから認証されたユーザーのアカウントを取得しようとします。
- アカウントが存在する場合、フィルタは認証されたユーザーのユーザー名をリクエストにユーザープリンシパルとして添付し、リクエストを許可します。
- 上記以外の場合、フィルタはログインページへのリダイレクトで応答します。
APIリクエスト用の新しいサーブレットフィルタの実装
com.authlete.simpleauth.oauthパッケージにOAuthFilter.javaという新しいJavaソースファイルを作成し、以下の内容を追加します:
-
フィルタは、
AuthorizationHTTPヘッダーが"Bearer "で始まる値を持っているかどうかを確認します(スペースに注意)。そのようなヘッダーがない場合は、これ以上の処理は必要ないため、リクエストをフィルタチェーンに渡します。 - フィルタは、HTTPヘッダーからアクセストークンを抽出し、Authlete APIのイントロスペクションエンドポイントに送信して検証します。実際のクライアントでは、パフォーマンスを向上させるために、イントロスペクションレスポンスをキャッシュすることもあります。
-
Authleteから返された
actionがエラーを示している場合、OAuthUtils.handleAuthleteApiCall()が適切なアクションをすでに実行しているため、アクセスを拒否した事実をログに記録して終了します。 -
APIレスポンスには、認証されたユーザーのユーザー名が
subjectプロパティに含まれています。それをリクエストに添付し、リクエストをチェーンに渡します。 - ここに到達することはありません!
イントロスペクションエラーに対応するエラーハンドリングの追加
Authleteの/auth/introspectionエンドポイントのドキュメントを確認すると、トークンが有効である場合、actionはOKとなり、レスポンスにはエンドユーザーを識別するsubjectプロパティが含まれることがわかります。また、追加のエラー値として、UNAUTHORIZEDおよびFORBIDDENが存在します。
UNAUTHORIZEDは、アクセストークンが認識されない、または期限切れであることを意味します。FORBIDDENは、アクセストークンが必要なスコープをカバーしていないか、アクセストークンに関連付けられた主体がリクエストに含まれる主体と異なることを意味します。このデモでは、FORBIDDENに該当する状況は発生しませんが、完全性を保つためにエラーハンドリングを含めています。
これらのエラーでは、認可サーバーはクライアントにWWW-Authenticate HTTPヘッダーでresponseContentの内容を返す必要があります。
OAuthUtils.handleAuthleteApiCall()にさらにエラーハンドリングを追加する必要があります。switch文を次のコードに置き換えます:
ログインフィルタを修正してユーザープリンシパルを確認
新しいOAuthフィルタがHTTPリクエストにユーザー名を設定するため、これらのリクエストを許可するように既存のログインフィルタを修正する必要があります。$SOURCE_ROOT/loyalty/src/main/java/com/authlete/simpleauth/LoginFilter.javaを開き、ステップ1と2の間に以下のコードを追加します:
フィルタチェーンにサーブレットフィルタを追加
最後のタスクは、APIリクエストの場合にOAuthフィルタがログインフィルタの前に呼び出されるようにすることです。$SOURCE_ROOT/loyalty/src/main/webapp/WEB-INF/web.xmlで、このフィルタマッピングをログインフィルタのマッピングの前に挿入します:
url-pattern is relative to the loyalty web application’s context root, /loyalty.
url-patternはロイヤルティウェブアプリケーションのコンテキストルート/loyaltyに対する相対パスであることに注意してください。
アプリケーションの再ビルドとテスト
再び、再ビルドスクリプトを実行してアプリを再ビルドおよび再デプロイします:
後処理
Dockerコンテナを停止する場合は、以下のコマンドを実行します:トラブルシューティング
問題が発生した場合、変更を破棄してソースをステップ3の終了時点に戻すか、ステップ4の終了時点のソースをチェックアウトしてください。 変更を破棄するには:レビュー
ロイヤルティプログラムのウェブアプリケーションに対して、OAuth 2.0の認可サーバーおよびリソースサーバーとしての機能を追加するためにコードを追加しました。しかし、追加したコードは非常に少なく、そのほとんどはクライアントのリクエストをAuthlete APIに渡し、APIのレスポンスをクライアントに返すだけのものでした。 以下はOAuth 2.0フローの全体像です:
ステップ1: 認可の開始

?以降のすべて)をAuthleteの/auth/authorizationエンドポイントに転送します。このサーブレットは、クエリ文字列を解析する必要はなく、OAuthパラメータについて知識を持つ必要もありません。
Authleteのレスポンスには、このあとの一連のやり取りを一意に識別するticket、サーブレットが取るべき動作を表すactionパラメーター、および、サーブレットの動作として取るべきプロンプトを表すpromptsパラメーターが含まれています。
このレスポンスは、ユーザーを認証し、次のステップに進むべきであることを示しています。
ステップ2: クライアントに認可コードを発行

reasonパラメーターにNOT_LOGGED_INを指定してAuthleteの/auth/authorization/failエンドポイントを呼び出します。この場合、Authleteは、LOCATIONのアクションと、クライアントのリダイレクトURLと、ユーザーがログインを要求されたが行われなかったことを示すエラーパラメータを含むURLを返します。
一方、すべてが正常であれば、サーブレットはステップ1で発行されたticketとユーザーのユーザー名を含むリクエストをAuthleteの/auth/authorization/issueエンドポイントに送信します。Authleteのレスポンスには値がLOCATIONとなっているactionパラメーターが含まれており、サーブレットはブラウザにHTTPステータス302 Foundを返し、responseContentに含まれるURL(認可コードをクエリ文字列として含む)にリダイレクトするよう指示します。
ここでも、サーブレットはOAuthプロトコルの詳細に関心を持つ必要はありません。サーブレットはクライアントとAuthleteの間の仲介者であり、各APIレスポンスに従って動作するだけです。
ステップ3: クライアントのアクセストークン要求を処理

/auth/tokenエンドポイントに送信します。この場合、サーブレットはリクエストの内容についての知識を持つ必要はありません。このリクエストは、例えば次のようなOAuthアクセストークンリクエストを含むフォームポストです:
ステップ4: クライアントのロイヤルティプログラムAPIへのリクエストを検証

/auth/introspectionエンドポイントに送信して検証します。Authleteがトークンを検証し、それが有効であれば、レスポンスにはOKアクションとusernameとして設定されたサブジェクトが含まれます。フィルタはそのユーザー名をリクエストに添付し、それをチェーンに渡します。
エラーが発生した場合は、Authleteが適切なactionとresponseContentを設定し、それらに基づいてロイヤルティアプリはレスポンスを返します。
リクエストがログインフィルタに到達すると、ユーザー名が存在することに基づいてリクエストを許可します。ロイヤルティAPIは、リクエスト内のユーザー名に基づいて関連データを返します。