Table of Contents
この文書は Authlete ベースの認可サーバー (AS) と外部のアイデンティティプロバイダー (IdP) との連携方法を説明するチュートリアルです。この連携により、Financial-grade API (FAPI) のような高いセキュリティの業界標準に準拠した API 認可基盤を構築すると同時に、外部 IdP のユーザー認証・管理機能を利用できるようになります。
この連携のしくみは、ユーザーの認証情報や属性情報などを IDaaS (Identity as a Service) を用いて既に維持管理しているサービス事業者が、英国オープンバンキング / オーストラリア消費者データ権 / ブラジルオープンバンキングなどのエコシステムが定義する FAPI ベースのセキュリティ要件を実装しなければならない場合に有用です。
このチュートリアルでは、OpenID Connect (OIDC) IdP として Okta、Authlete を利用する AS として java-oauth-server を用い、連携方法の例を説明します。
このチュートリアルを始める前に次のアカウントを用意しておいてください。
また、java-oauth-server をインストールして実行する環境も必要となります。
OAuth 2.0 (RFC 6749) と OIDC (OpenID Connect Core 1.0) の仕様は、AS がユーザーをどのように認証するかについては意図的に定義していません。そのため AS は、ユーザー認証を外部 OIDC IdP に委譲し、当 IdP からユーザー情報を取得するという手段をとるかもしれません。下図は、そのような AS と外部 OIDC IdP との間の、アイデンティティ (ID) 連携のフローを示しています。
図に示したフローの各手順は以下の通りです。
Location
ヘッダーをつけて 302 Found
(リダイレクションを促すものであれば何でもよい) をウェブブラウザに返す。これによりウェブブラウザは IdP の認可エンドポイントに認証リクエストを送ることになる。Location
ヘッダーをつけて 302 Found
(リダイレクションを促すものであれば何でもよい) をウェブブラウザに返す。これによりウェブブラウザは (クライアントのものではなく) AS のリダイレクション URI に認可コード付きでアクセスする。IdP の視点からは、AS はクライアントアプリケーションであり、当該リダイレクション URI は AS が事前に IdP に登録しておいたものの一つである。Location
ヘッダーをつけて 302 Found
(リダイレクションを促すものであれば何でもよい) をウェブブラウザに返す。これによりウェブブラウザは (AS のものではなく) クライアントのリダイレクション URI に認可コード付きでアクセスする。ここではクライアントが少なくとも一つのリダイレクション URI を事前に AS に登録済みであることを想定している。次の節では、Okta を IdP として設定し、Authlete をバックエンドとして用いる AS (java-oauth-server) と連動させて上図のフローを実現する方法を紹介します。
OIDC 互換 IdP のクライアントアプリケーションとして AS が動作できるよう、Okta 上で稼働するサーバーに「App Integration」を作成する必要があります。
下表は App Integration 設定の要点を示しています。
項目 | 値 |
---|---|
Sign-in method | OIDC - OpenID Connect |
Application Type | Web Application |
Sign-in redirect URIs | http://localhost:8080/api/federation/callback/okta |
Controlled access | Allow everyone in your organization to access |
Sign-in redirect URIs の値を見てお気付きかもしれませんが、このチュートリアルの後半でローカルマシン上のポート番号 8080 で AS を動かすことになります。また、その AS は /api/federation/callback/okta
API を提供します。
次の画像群は App Integration 設定のスクリーンショットです。
App Integration 生成後、Okta はクライアント ID とクライアントシークレットの組を生成します。次節でこの組を利用します。
このチュートリアルでは java-oauth-server を AS として用います。java-oauth-server はオープンソースの AS サンプル実装で、Authlete をバックエンドとして利用します。
まず初めに java-oauth-server をダウンロードして authlete.properties
を編集してください。少なくとも同ファイル内の service.api_key
と service.api_secret
の値を変更する必要があります。
$ git clone https://github.com/authlete/java-oauth-server
$ cd java-oauth-server
$ vi authlete.properties
次に federations.json
をテキストエディタで開いてください。このファイルに ID 連携の設定を記述します。内容の初期値は下記のようになっています。YOUR_COMPANY
、YOUR_CLIENT_ID
、YOUR_CLIENT_SECRET
を Okta から割り当てられた実際の値で置き換えてください。federations.json
の詳細については「付録 / federations.json」を参照してください。
{
"federations": [
{
"id": "okta",
"server": {
"name": "Okta-hosted IdP",
"issuer": "https://YOUR_COMPANY.okta.com"
},
"client": {
"clientId": "YOUR_CLIENT_ID",
"clientSecret": "YOUR_CLIENT_SECRET",
"redirectUri": "http://localhost:8080/api/federation/callback/okta",
"idTokenSignedResponseAlg": "RS256"
}
}
]
}
必要な設定は以上です。ローカルマシン上で java-oauth-server を起動してください。
$ docker-compose up
# もしくは mvn jetty:run
次のようなログが確認できれば、java-oauth-server は準備完了です。
app_1 | [INFO] Started ServerConnector@7c5ae5c3{HTTP/1.1, (http/1.1)}{0.0.0.0:8080}
app_1 | [INFO] Started @41702ms
app_1 | [INFO] Started Jetty Server
「 AS と IdP の ID 連携」で示した図では、クライアントは OIDC 認可コードフロー (response_type=code
) を用いています。しかしここでは、Okta との ID 連携が機能していることを素早く確認するため、ID トークンのみを発行する OIDC インプリシットフロー (response_type=id_token
) を使用します。
次の URL は認可リクエストです。CLIENT_ID
をAuthlete から発行されたクライアント ID (Okta から発行されたものではない)、SERVICE_API_KEY
を Authlete から発行されたサービス API キーで置き換え、URL をウェブブラウザのアドレスバーに貼り付けてください。
http://localhost:8080/api/authorization?client_id=CLIENT_ID&response_type=id_token&scope=address+email+openid+phone+profile&redirect_uri=https://api.authlete.com/api/mock/redirection/SERVICE_API_KEY&nonce=mynonce&state=mystate&prompt=login
次のような認可ページが表示されます。Authorize ボタンの上に表示されている「Okta-hosted IdP」リンクをクリックしてください。
ウェブブラウザは Okta が生成した次のような認可ページを表示します。Okta 上で稼働しているサーバーにログインする際に利用するログイン ID とパスワードを入力してください。
ウェブブラウザは java-oauth-server が生成した元々の認可ページを再表示します。この際、java-oauth-server はあなたがログイン済みと認識しています。Authorize ボタンを押してください。
java-oauth-server は ID トークンを発行し、ブラウザをリダイレクションエンドポイントにリダイレクトします。Authlete サーバー上で提供されているリダイレクションエンドポイントのモック実装 (/api/mock/redirection/サービスAPIキー
) は受け取ったパラメーター群を表示します。
リクエストに暗号化されていない ID トークンが含まれていれば、リダイレクションエンドポイントはその ID トークンのペイロード部をデコードし、次のように表示します。
次の表は、発行された ID トークン内のクレーム群のデータ提供元を示しています。ユーザー属性に関する属性は全て Okta 由来です。
クレーム | データ提供元 |
---|---|
email |
Okta |
email_verified |
Okta |
family_name |
Okta |
given_name |
Okta |
locale |
Okta |
name |
Okta |
phone_number_verified |
Okta |
preferred_username |
Okta |
updated_at |
Okta |
zoneinfo |
Okta |
iss |
Authlete / Service 設定, issuer |
sub |
java-oauth-server / Authlete の /api/auth/authorization/issue API に渡された subject パラメーター |
aud |
Authlete / Client 設定, clientId (変更不可) |
exp |
Authlete / ID トークンの有効期間終了時刻 |
iat |
Authlete / ID トークンの発行時刻 |
auth_time |
java-oauth-server / Authlete の /api/auth/authorization/issue API に渡された authTime パラメーター |
nonce |
クライアント / nonce リクエストパラメーターの値 |
s_hash |
Authlete / state リクエストパラメーターの値に基づいて計算 |
sub
クレームの値 (例内の "00ucabhbjLVphPrXF696@okta"
) がどこから来たのか気になられるかもしれません。この値は、Okta が返した sub
クレームの値と文字列 "@okta"
を連結した結果に過ぎません。連結は FederationEndpoint.java の createUserEntity()
メソッドで実行されています。これに大きな意味はなく、sub
クレームの値をどのように決めるかはあなた次第です。
本チュートリアルでは、Authlete ベースの認可サーバー (AS) と外部のアイデンティティプロバイダー (IdP) との連携パターンを、java-oauth-server と Okta を用いて、実際の動作を確認しました。
ID 連携の実装レベルの詳細については java-oauth-server の Federation.java と FederationEndpoint.java を調べることで理解できます。
OIDC IdP のユーザー情報エンドポイントからユーザー情報を受け取ったあと、java-oauth-server の現実装はメモリ上のユーザーデータベースにユーザーレコードを登録します。しかし、商用実装では他の選択肢もありうるでしょう。
例えば、OIDC IdP から得たユーザーデータを手元のデータベースに複製することを避け、かわりに IdP から発行されたアクセストークンを覚えておき、後ほど好きな時に IdP のユーザー情報エンドポイントから最新のユーザー情報を取り出せるようにするかもしれません。
そのような動作を実装するため、AS は IdP から発行されたアクセストークンを覚えておくデータベースを用意するかもしれません。その他にも、アクセストークンに任意のキー・バリュー群を関連付ける Authlete 独自の Extra Properties 機能を用い、IdP が発行したアクセストークンを Authlete に覚えておかせるという方法も考えられます (参照→『トークンに任意のプロパティをひもづける方法』)。
federations.json
は ID 連携の設定を記述するための設定ファイルです。java-oauth-server は最初に認可リクエストを受け取ったときに当ファイルを読み込みます。ファイル内に列挙された ID 連携は java-oauth-server の認可ページ内に表示されます。
ファイルのデフォルトの位置は "fderations.json"
です。環境変数 FEDERATIONS_FILE
およびシステムプロパティー federations.file
を用いて他の場所を指定することができます。
federations.json
の内容は、"federations"
を最上位プロパティーとして持つ JSON オブジェクトでなければなりません。
{
"federations": [
"(ID 連携の設定群)"
]
}
"federations"
配列の各要素は ID 連携の設定を表し、"id"
、"server"
、"client"
を最上位プロパティーとして持つ JSON オブジェクトです。
{
"federations": [
{
"id": "(設定群内で一意となる識別子)",
"server": {
"(サーバー設定)"
},
"client": {
"(クライアント設定)"
}
}
]
}
"id"
の値は、java-oauth-server の次の API パス内の federationId
部で使用されます。
/api/federation/initiation/federationId
/api/federation/callback/federationId
"server"
には対象の IdP の設定を記述します。値は JSON オブジェクトで、下表のプロパティー群を持ちます。
プロパティー | 説明 |
---|---|
name |
IdP の表示名。認可ページで利用されます。 |
issuer |
IdP の発行者識別子。この値は IdP のディスカバリードキュメント内の "issuer" の値と一致していなければなりません。java-oauth-server の ID 連携を機能させるためには、IdP が {issuer}/.well-known/openid-configuration でディスカバリードキュメントを公開していなければなりません。 |
"client"
はクライアントの設定を記述するもので、ここでクライアントは常に java-oauth-server を指します。外部 IdP から見ると java-oauth-server はクライアントアプリケーションであることに留意してください。"client"
の値は JSON オブジェクトで、下表のプロパティー群を持ちます。
プロパティー | 説明 |
---|---|
clientId |
IdP が発行したクライアント ID。 |
clientSecret |
IdP が発行したクライアントシークレット。このプロパティーがセットされている場合、IdP のトークンエンドポイントに送られるトークンリクエストにクライアント認証用の Authorization ヘッダーが含まれるようになります。この動作はトークンエンドポイントがクライアント認証方式として client_secret_basic をサポートしていることを前提としています。 |
redirectUri |
IdP に事前登録してあるリダイレクション URI。 |
idTokenSignedResponseAlg |
IdP が ID トークンに署名する際に使用するアルゴリズム。このプロパティーが省略された場合、デフォルト値として "RS256" が使用されます。 |