FAPI Basics Supplement: Integration with Reference Implementations

はじめに

本書では、チュートリアル「Financial-grade API Basics」(以下 “FAPI Basics”) の設定を行った Authlete サービスにリファレンス実装を統合し、 FAPI 準拠の認可フローと API リクエストの動作を確認します。

チュートリアルの構成

前提条件

手順の実施にあたっては、以下のチュートリアルの実施とナレッジベース記事の通読を行い、 OpenID Connect と Authlete に関する基本的な理解を有していることが必須です。

また、以下のチュートリアル (FAPI Basics) の手順に従って、 Authlete が設定されている必要があります。 とくに、設定した環境が「設定完了後の実行例」の記述通りに動作することを確認してください。

本チュートリアルでは、動作確認に以下のソフトウェアを用います。 これらを実行可能な環境をご用意ください。

  • チュートリアル実施に必須
  • 同等の機能を有する別のソフトウェアにて代替も可能
    • Apache HTTP Server (以下 Apache): 認可サーバーおよびリソースサーバーのリバースプロキシー
    • curl: API クライアント
    • OpenSSL: 秘密鍵・公開鍵証明書の生成

認可サーバーの設定

本セクションでは以下の作業を行います。

  • java-oauth-server の導入
  • 接続テスト

設定後の構成は以下の図の通りです。 Web ブラウザと curl は、それぞれ認可リクエスト・トークンリクエストを、 認可サーバーである java-oauth-server (http://localhost:8080) に送信します。

認可サーバー (java-oauth-server) 設定後の構成

java-oauth-server の設定

java-oauth-server のドキュメントに従い、ダウンロードと設定を行います。

ダウンロード

git コマンドを用いて java-oauth-server をダウンロードします。

$ git clone https://github.com/authlete/java-oauth-server.git

設定

設定ファイル authlete.properties の以下の行を編集します。 FAPI Basics の設定が済んでいる Authlete サービスの、API キーと API シークレットを指定してください。

service.api_key = <your API key e.g. 1450...0338>
service.api_secret = <your API key e.g. VEIl...0cEs>

起動

mvn コマンドを用いて java-oauth-server を起動します。

$ mvn jetty:run

動作確認

Web ブラウザから http://localhost:8080 にアクセスし、以下のページが表示されることを確認します。

java-oauth-server のトップページ

FAPI 認可フローの実行 (1)

FAPI Basics チュートリアルの手順に従いリクエストオブジェクトを作成し、認可リクエストを組み立てます。本チュートリアルにて用いている内容は以下の通りです。

{
"redirect_uri":"https://client.example.org/cb/example.com",
"response_type":"code id_token",
"client_id":"1756...3766",
"scope":"openid payment",
"exp":15549730000,
"aud":"https://as.example.com",
"claims":{
  "id_token":{
    "acr":{
      "essential":true,
      "values":["urn:example:psd2:sca"]
    }
  }
},
"nonce":"n-0S6_WzA2Mj"
eyJr...YifQ.ewoi...iCn0.ztl2...tjAA
  • 認可リクエスト(client_idrequest パラメーターの値 (それぞれ 1756...3766, eyJr...YifQ.ewoi...iCn0.ztl2...tjAA) を適宜変更してください)
http://as.example.com:8080/api/authorization
 ?redirect_uri=https://client.example.org/cb/example.com
 &scope=openid+payment
 &response_type=code+id_token
 &client_id=1756...3766
 &nonce=n-0S6_WzA2Mj
 &request=eyJr...YifQ.ewoi...iCn0.ztl2...tjAA

生成した認可リクエストの URL を、Web ブラウザのロケーションに入力・送信します。

認可リクエストの URL を入力・送信

認可ページが表示されます。 Login ID / Password に、それぞれ john / john と入力し、Authorize ボタンをクリックします。

java-oauth-server の認証・認可ページ

https://client.example.org/cb/example.com/#code=... にリダイレクトされます。しかし、 ホスト client.example.org は存在しないため、Web ブラウザはエラーページを表示します。

client.example.org が見つからない旨のエラー

認可レスポンス

Web ブラウザの表示したエラーページの URL をコピーします。本チュートリアルでは以下の内容になります。

https://client.example.org/cb/example.com#code=0Io2....sdkQ&id_token=eyJr...YifQ.eyJz...aiJ9.-EPu...Dkkw

この URL から code パラメーターを取り出します。

code=0Io2...sdkQ

トークンリクエスト

先に得た code の値を含むトークンリクエストを組み立て、java-oauth-server のトークンエンドポイント (/api/token) に送信します。 ここではまず、client_id / client_secret によるクライアント認証を試みます。 本チュートリアルでの各値は 0Io2...sdkQ, 1756...3766, EXE7...UxHg となっていますが、適宜変更してください。

$ curl -s -X POST http://localhost:8080/api/token \
 -u '1756...3766:EXE7...UxHg' \
  --data-urlencode 'grant_type=authorization_code' \
  --data-urlencode 'redirect_uri=https://client.example.org/cb/example.com' \
  --data-urlencode 'code=0Io2...sdkQ'

トークンレスポンス

上記のトークンリクエストの結果、トークンレスポンスは以下のような内容になります。

{
  "error_description": "[A244308] Because the feature of certificate-bound 
  access tokens is enabled, the client must establish a mutual TLS connection
  to the token endpoint.",
  "error": "invalid_request",
  "error_uri": "https://docs.authlete.com/#A244308"
}

実際の実行例

クライアント(本チュートリアルでは curl コマンド)と java-oauth-server との接続が相互 TLS ではないため、java-oauth-server はクライアント証明書を取得できませんでした。結果的に java-oauth-server から Authlete API (/auth/token API) へのリクエストにもクライアント証明書は含まれず、クライアント証明書にひもづくアクセストークンが発行できなかったため、Authlete が A244308 エラー を返却しています。

このエラーを解消するために、次のセクションにて、java-oauth-server がクライアント証明書を受け取れるように設定していきます。

認可サーバーの TLS 化

本セクションでは以下の作業を行います。

  • リバースプロキシーの設定
  • クライアントの設定

設定後の構成は以下の図の通りです。 先のセクションでは、Web ブラウザとクライアントの接続先は java-oauth-server (http://localhost:8080) でしたが、設定後は リバースプロキシーとして前段に配置した Apache (https://as.example.com:8443) に変更します。 また curl については、Apache への接続時にクライアント認証を試みます。

認可サーバーの TLS 化

リバースプロキシーの設定 (1)

FQDN の設定

この後ローカル環境にインストールする Apache に対して、Web ブラウザと curl が as.example.com という FQDN でアクセスできるように、本チュートリアルでは /etc/hosts に以下を追加します。

127.0.0.1 as.example.com

Apache のインストール

チュートリアルの実行環境に Apache をインストールします。

$ brew install httpd

本チュートリアルでは、Apache の設定ファイルが /usr/local/etc/httpd 以下に配置されたものとします。

サーバ証明書の作成・配置

RSA 秘密鍵と公開鍵証明書の生成

OpenSSL を用いて、Apache に設定する秘密鍵と公開鍵証明書を生成します。

$ openssl req -x509 -newkey rsa:2048 \
 -keyout server.key -out server.crt 
 -days 3650 -sha256 -nodes \
 -subj '/C=JP/ST=Tokyo/L=Chiyoda-ku/O=Server/CN=as.example.com'
Generating a 2048 bit RSA private key
..........................+++
....+++
writing new private key to 'server.key'
-----

この結果、server.key(秘密鍵)と server.crt(公開鍵証明書)の 2 つのファイルが生成されます。

秘密鍵と公開鍵証明書の配置

生成された 2 つのファイルを任意の場所に配置します。本チュートリアルでは配置場所を /usr/local/etc/httpd とします。

$ sudo cp -p server.key server.crt /usr/local/etc/httpd/.
Password:

Apache の TLS 設定

httpd.conf

本チュートリアルでは Apache の基本的な設定として以下を行います。

  • ポート 8080 をリッスンしない(java-oauth-server が 8080 を使うため)
  • mod_proxy, mod_ssl 関連を有効化(リバースプロキシーかつ TLS 接続を受けつけるため)

設定ファイル httpd.conf に対する具体的な変更点は以下の通りです。 TLS 関連の設定は別のファイル (extra/httpd-ssl.conf) に記述した内容をインクルードします。

$ diff /usr/local/etc/httpd/httpd.conf.orig \
/usr/local/etc/httpd/httpd.conf
52c52
< Listen 8080
---
> #Listen 8080
92c92
< #LoadModule socache_shmcb_module lib/httpd/modules/mod_socache_shmcb.so
---
> LoadModule socache_shmcb_module lib/httpd/modules/mod_socache_shmcb.so
131c131
< #LoadModule proxy_module lib/httpd/modules/mod_proxy.so
---
> LoadModule proxy_module lib/httpd/modules/mod_proxy.so
134c134
< #LoadModule proxy_http_module lib/httpd/modules/mod_proxy_http.so
---
> LoadModule proxy_http_module lib/httpd/modules/mod_proxy_http.so
150c150
< #LoadModule ssl_module lib/httpd/modules/mod_ssl.so
---
> LoadModule ssl_module lib/httpd/modules/mod_ssl.so
524c524
< #Include /usr/local/etc/httpd/extra/httpd-ssl.conf
---
> Include /usr/local/etc/httpd/extra/httpd-ssl.conf

extra/httpd-ssl.conf

本チュートリアルでは Apache の TLS 関連の設定として以下を行います。

  • FQDN を as.example.com に変更(ポートは 8443 のまま)
  • FAPI 準拠(TLS 1.2 に限定し、また使用可能な暗号スイートを制限)
  • クライアント証明書を X-Ssl-Cert ヘッダに格納(このヘッダにクライアント証明書がセットされている場合、java-oauth-server の /api/token エンドポイントはそれを抽出し、Authlete API (/auth/token API) へのリクエストに含める)
  • https://as.example.com:8443/ へのリクエストを http://localhost:8080/ (java-oauth-server) に転送するリバースプロキシーとして設定

設定ファイル extra/httpd-ssl.conf に対する具体的な変更点は以下の通りです。

$ diff /usr/local/etc/httpd/extra/httpd-ssl.conf.orig \
 /usr/local/etc/httpd/extra/httpd-ssl.conf
125c125
< ServerName www.example.com:8443
---
> ServerName as.example.com:8443
289a290,302
> SSLEngine on
> SSLProtocol TLSv1.2
> SSLCipherSuite -ALL:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA256
> SSLCertificateKeyFile "/usr/local/etc/httpd/server.key"
> SSLCACertificateFile "/usr/local/etc/httpd/server.crt"
> SSLVerifyClient optional_no_ca
> SSLOptions +StdEnvVars +ExportCertData
> SSLCompression off
> RequestHeader set X-Ssl-Cipher "%{SSL_CIPHER}e" env=SSL_CIPHER
> RequestHeader set X-Ssl-Cert "%{SSL_CLIENT_CERT}e" env=SSL_CLIENT_CERT
> ProxyPreserveHost on
> ProxyPass "/" "http://localhost:8080/"
> ProxyPassReverse "/" "http://localhost:8080/"

Apache の起動

apachectl コマンドを用いて Apache を起動します。

$ apachectl start

起動後、Web ブラウザを用いて https://as.example.com:8443 にアクセスし、 http://localhost:8080 と同一の、java-oauth-server のトップページが表示されることを確認します。

次のセクションでは、トークンリクエストを送信するクライアント (curl) のための、 秘密鍵と公開鍵証明書を準備します。

クライアントの設定

クライアント証明書の作成

RSA 秘密鍵と公開鍵証明書の生成

以下のコマンドを実行し、秘密鍵と公開鍵証明書を生成します。

$ openssl req -x509 -newkey rsa:2048 \
 -keyout client.key -out client.crt 
 -days 3650 -sha256 -nodes \
 -subj '/C=JP/ST=Tokyo/L=Chiyoda-ku/O=Server/CN=client.example.org'
Generating a 2048 bit RSA private key
..........................+++
....+++
writing new private key to 'client.key'
-----

この結果、client.key(秘密鍵)と client.crt(公開鍵証明書)の 2 つのファイルが生成されます。 これらを、curl を用いたトークンリクエスト送信の際に用います。

FAPI 認可フローの実行 (2)

認可リクエスト・認可レスポンス

FAPI 認可フローの実行 (1) と同様に認可リクエストを行います。

返却される認可レスポンスから code パラメーターの値を抽出します。本チュートリアルでは次の値となります。

7Y5h...zp_4

トークンリクエスト

トークンリクエストを組み立て、java-oauth-server のトークンエンドポイント (/api/token) に送信します。 FAPI 認可フローの実行 (1) とは以下の点が異なります。

  • http://localhost:8080 ではなく、https://as.example.com:8443 に接続する
  • client_secret ではなく、秘密鍵 (client.key) および公開鍵証明書 (client.crt) を用いる

結果的に、実行する curl コマンドは以下のようになります。 (client_id, code の値は適宜変更してください)

$ curl --insecure -X POST https://as.example.com:8443/api/token \
--key client.key --cert client.crt \
--data-urlencode 'client_id=1756...3766' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'redirect_uri=https://client.example.org/cb/example.com' \
--data-urlencode 'code=7Y5h...zp_4'

トークンレスポンス

上記のトークンリクエストの結果、トークンレスポンスは以下のような内容になります。

{
  "access_token": "nbN3...LAU8",
  "refresh_token": "SWUu...V8_w",
  "scope": "openid payment",
  "id_token": "eyJr...YifQ.eyJz...qIn0.GPJr...STPQ",
  "token_type": "Bearer",
  "expires_in": 86400
}

クライアント証明書を指定し、相互 TLS 接続を行った結果、アクセストークン(本チュートリアルでは nbN3...LAU8) を取得できました。この処理の裏では、Authlete が、アクセストークンとクライアント証明書をひもづけています。

次のセクションでは、アクセストークンを用いてリソースサーバーの API にアクセスを試みます。

リソースサーバーの設定

本セクションでは以下の作業を行います。

  • java-resource-server の導入
  • 接続テスト

設定後の構成は以下の図の通りです。

リソースサーバー (java-resource-server) 設定後の構成

java-resource-server の設定

java-resource-server のドキュメントに従い、ダウンロードと設定を行います。

ダウンロード

git コマンドを用いて java-resource-server をダウンロードします。

$ git clone https://github.com/authlete/java-resource-server.git

設定

設定ファイル authlete.properties の以下の行を編集します。 java-oauth-server に設定したものと同じ API キーと API シークレットを指定してください。

service.api_key = <your API key e.g. 1450...0338>
service.api_secret = <your API key e.g. VEIl...0cEs>

起動

mvn コマンドを用いて java-resource-server を起動します。

$ mvn jetty:run

動作確認

Web ブラウザから http://localhost:8081 にアクセスし、以下のページが表示されることを確認します。

java-resource-server のトップページ

API リクエストの実行 (1)

curl コマンドを用いて、java-resource-server の「カントリーエンドポイント」に、アクセストークン付きの API リクエストを試みます。 まず FAPI 認可フローの実行 (2) によりアクセストークンを取得します。 本チュートリアルでは以下の値となります。

6Ivp...LiM4

このアクセストークンを Authorization ヘッダーにセットし、API リクエストを行います。 以下はリクエスト・レスポンスの例です。curl コマンドに -v オプションを付与し、 ヘッダー情報を出力しています。

$ curl -v -X GET http://localhost:8081/api/country/JP \
-H 'Authorization: Bearer 6Ivp...LiM4' 
...
> GET /api/country/JP HTTP/1.1
> Host: localhost:8081
> User-Agent: curl/7.64.1
> Accept: */*
> Authorization: Bearer 6Ivp...LiM4
> 
< HTTP/1.1 401 Unauthorized
< Date: Mon, 18 May 2020 02:22:32 GMT
< Cache-Control: no-store, no-transform
< Pragma: no-cache
< WWW-Authenticate: Bearer error="invalid_token",
 error_description="[A065304] The client failed to present 
 a certificate and the access token is MTLS bound.",
 error_uri="https://docs.authlete.com/#A065304"
< Content-Length: 0
< Server: Jetty(9.3.7.v20160115)
...

クライアント(本チュートリアルでは curl コマンド)と java-resource-server との接続が相互 TLS ではないため、java-resource-server はクライアント証明書を取得できませんでした。結果的に java-resource-server から Authlete API /auth/introspection API へのリクエストにもクライアント証明書は含まれず、クライアント証明書とアクセストークンとのひもづけが確認できなかったため、Authlete が A065304 エラー を返却しています。

このエラーを解消するために、次のセクションにて、java-resource-server がクライアント証明書を受け取れるように設定していきます。

リソースサーバーの TLS 化

本セクションでは以下の作業を行います。 基本的には、前半のセクションにて java-oauth-server に関して行った設定と同様です。

  • リバースプロキシーの追加設定

設定後の構成は以下の図の通りです。 リバースプロキシーとなる Apache (https://rs.example.com:8443) を経由して java-resource-server に接続するようにします。 また curl は Apache への接続時にクライアント認証を試みます。

リソースサーバーの TLS 化

リバースプロキシーの設定 (2)

FQDN の設定

Apache に対して、curl が rs.example.com という FQDN でアクセスできるよう、リバースプロキシーの設定 (1) にて追加した /etc/hosts の行に、以下のように追記します。

127.0.0.1 as.example.com rs.example.com

Apache の TLS 設定

extra/httpd-ssl.conf

Apache の TLS 関連の設定として、以下を extra/httpd-ssl.conf ファイルに追記します。 サーバー名やプロキシー先を除けば、先のリバースプロキシーの設定 (1) における内容と同様です。

  • rs.example.com:8443 に対する処理の内容を定義
  • FAPI 準拠(TLS 1.2 に限定し、また使用可能な暗号スイートを制限)
  • クライアント証明書を X-Ssl-Cert ヘッダに格納(このヘッダにクライアント証明書がセットされている場合、java-resource-server の /api/country エンドポイントはそれを抽出し、Authlete API (/auth/introspection API) へのリクエストに含める)
  • https://rs.example.com:8443/ へのリクエストを http://localhost:8081/ (java-resouce-server) に転送するリバースプロキシーとして設定

具体的な追加内容は以下の通りです。

<VirtualHost _default_:8443>
DocumentRoot "/usr/local/var/www"
ServerName rs.example.com:8443
ServerAdmin you@example.com
ErrorLog "/usr/local/var/log/httpd/error_log"
TransferLog "/usr/local/var/log/httpd/access_log"
SSLEngine on
SSLProtocol TLSv1.2
SSLCipherSuite -ALL:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA256
SSLCertificateKeyFile "/usr/local/etc/httpd/server.key"
SSLCertificateFile "/usr/local/etc/httpd/server.crt"
SSLCACertificateFile "/usr/local/etc/httpd/server.crt"
SSLVerifyClient optional_no_ca
SSLOptions +StdEnvVars +ExportCertData
SSLCompression off
RequestHeader set X-Ssl-Cipher "%{SSL_CIPHER}e" env=SSL_CIPHER
RequestHeader set X-Ssl-Cert "%{SSL_CLIENT_CERT}e" env=SSL_CLIENT_CERT
ProxyPreserveHost on
ProxyPass "/" "http://localhost:8081/"
ProxyPassReverse "/" "http://localhost:8081/"
</VirtualHost>                                  

Apache の起動

apachectl コマンドを用いて Apache を停止・起動します。

$ apachectl stop && apachectl start

起動後、Web ブラウザを用いて https://rs.example.com:8443 にアクセスし、 http://localhost:8081 と同一の、java-resource-server のトップページが表示されることを確認します。

API リクエストの実行 (2)

API リクエストの実行 (1) と同様に、 curl コマンドを用いて API リクエストを試みます。 まず FAPI 認可フローの実行 (2) によりアクセストークンを取得します。 本チュートリアルでは以下の値となります。

6Ivp...LiM4

このアクセストークンを Authorization ヘッダーにセットし、API リクエストを行います。 先ほどとは以下の点が異なります。

  • http://localhost:8081 ではなく、https://rs.example.com:8443 に接続する
  • アクセストークンに加えて、秘密鍵 (client.key) および公開鍵証明書 (client.crt) も用いる

結果的に、実行する curl コマンドは以下のようになります。

$ curl -v -X GET http://localhost:8081/api/country/JP \
-H 'Authorization: Bearer 6Ivp...LiM4' \
--key client.key --cert client.crt --insecure
...

実行後、API レスポンスとして以下の内容が返却されれば、動作確認は完了です。

{
  "name": "Japan",
  "alpha2": "JP",
  "alpha3": "JPN",
  "numeric": 392,
  "currency": "JPY"
}

実際の実行例

相互 TLS 接続を行うことにより、クライアント証明書とアクセストークンとのひもづけが確認でき、 API リクエストが期待通り処理されたことを示しています。

まとめ

本チュートリアルでは、FAPI Basics の設定を行った Authlete サービスをバックエンドとし、 リファレンス実装 (java-oauth-server, java-resource-server) とリバースプロキシーを用いて、 FAPI 準拠の認可フローと API リクエストの動作を確認しました。