2024年1月19日金曜日

ORDS_SECURITY.CREATE_JWT_PROFILEを使ってRESTサービスを保護する

Oracle REST Data Services 23.3から、サードパーティのIdPが生成したOAuth2のアクセス・トークンによる保護が実装されています。

ドキュメントの以下に説明が記載されています。

2.10 JWT Bearer Token Authentication and Authorization Using JWT Profile

保護を設定するために、ORDS_SECURITY.CREATE_JWT_PROFILEを呼び出します。

Oracle APEXのアプリケーションの認証スキームをOpen ID Connectで構成して、動作を確認してみました。

Microsoft Entra ID:
Microsoft Entra IDのOIDC認証にてAPEXアプリとそれから呼び出すORDSのREST APIを認証する

Okta:

Oracle IAM:
Oracle IAMのOIDC認証にてAPEXアプリとそれから呼び出すORDSのREST APIを認証する

以下より、上記の動作確認に使用したRESTサービスおよびAPEXアプリケーションについて説明します。APEXアプリケーションのエクスポートは以下に置いてあります。
https://github.com/ujnak/apexapps/blob/master/exports/ords-jwt-test.zip

最初に、保護の対象としているORDSのRESTサービスの作成手順を説明します。

以下のRESTfulデータ・サービスを作成します。

モジュール名: print
モジュール・ベース・パス: /print/
URLテンプレート: env
メソッド: GET
ソース・タイプ: PL/SQL

ソースに以下を記述します。
begin
    htp.p('<p>' || coalesce(:current_user, 'no_current_user') || '</p>');
    owa_util.print_cgi_env;
end;
最初にバインド変数current_userの値を表示しています。current_userの値はJWTのサブジェクト(属性sub)の値が設定されることになっています。また、この値はCGI変数のREMOTE_IDENTにも設定されます。

続けて、RESTサービスが受け取ったHTTPリクエストのヘッダーを出力しています。


完全なURLをブラウザで開いて、HTTPヘッダーの情報が表示されることを確認します。

認証が必要な保護がかけられていないため、バインド変数current_userはNULLになっています。同様にCGI変数REMOTE_IDENTも空白になっています。


権限を作成し、作成したRESTサービスprintを保護します。

権限の名前myordsappとします。

権限の名前はOAuth2のアクセス・トークンのスコープ(属性名としてはscope)に含まれるよう設定します。アイデンティティ・プロバイダーでは、カスタム・スコープとしてmyordsappを作成します。APEXアプリケーションでは、要求するスコープにmyordsappが含まれるように設定します。

タイトルは任意の文字列です。

ロールとしてRESTful Services保護されたモジュールprintを選択します。

以上の設定で権限を作成します。


再度、完全なURLにアクセスします。HTTP 401、Unauthorizedのエラーが返されます。


認証スキームに使用するWeb資格証明を、APEXのワークスペースに作成します。


認証タイプとしてOAuth2クライアント資格証明フローを選択します。

クライアントIDまたはユーザー名およびクライアント・シークレットまたはパスワードとして、認証サービスにアプリケーションを作成した際に割り当てられたクライアントIDクライアント・シークレットを設定します。


テストに使用するAPEXアプリケーションを作成します。名前ORDS JWT Testとします。

ホーム・ページに、アイデンティティ・プロバイダーが返すレスポンスとREST APIのレスポンスを表示します。

ホーム・ページは以下のように表示されます。アイデンティティ・プロバイダーが返すaccess_tokenと、REST APIのレスポンスに含まれるAuthorizationヘッダに含まれるBearerトークンは、同じ内容になります。


作成したアプリケーションには記事「Open ID ConnectまたはOAuth2による認証の応答を印刷する」で紹介している、デバッグのための仕組みを組み込みます。ファンクションdump_signon_responseが作成済みとします。

ファンクションdump_signon_responseの出力を保存するアプリケーション・アイテムとしてG_USER_INFOを作成します。


アプリケーション定義置換置換文字列としてG_WALLET_PATHが作成されています。APEX_WEB_SERVICE.MAKE_REST_REQUESTの引数p_wallet_pathの値になります。通常は設定不要です。


各種のアイデンティティ・プロバイダーに対応した認証スキームを作成しています。それぞれの設定は、アイデンティティ・プロバイダーに依存します。


ホーム・ページに、アイデンティティ・プロバイダーのレスポンスを表示するページ・アイテムを作成します。

名前P1_USER_INFOタイプとしてテキスト領域を選択します。外観高さ20を設定し、テキスト領域の高さを大きくしておきます。

ソースタイプアイテムを選択し、アイテムとしてG_USER_INFOを指定します。データ型CLOBを選択します。


REST APIのレスポンスを表示するリージョンを作成します。

識別タイトルREST API Responseタイプとして動的コンテンツを選択します。

ソースCLOBを返すPL/SQLファンクション本体に、以下のコードを記述します。

表示されるデータ量が多いため、テンプレート・オプションBody Heightとして320pxにしています。


アイデンティティ・プロバイダーに作成するアプリケーションに、ログアウト時のリダイレクト先として設定できるページを作成します。ページ名はLOGOUTとします。


このページはログインのページと同様に、セキュリティ認証パブリック・ページに設定し、APEXセッションなしでアクセス可能にします。


ページにはホームへ戻るボタンのみを作成しています。ボタンHOME動作アクションこのアプリケーションのページにリダイレクトターゲットページ1です。


以上のようにログアウト時の宛先となるページを作成した上で、認証スキームログアウト後URLとして、以下を設定します。このURLをアイデンティティ・プロバイダーのアプリケーションのログアウト時のリダイレクト先として設定します。

f?p=ORDS-JWT-TEST:LOGOUT:0::NO:::


以上でアプリケーションは完成です。

ORDSのRESTサービスprintおよびPL/SQLファンクションdump_sigon_responseとその関連オブジェクトは、APEXアプリケーションをインポートするときにサポートするオブジェクトとして作成されます。

サポートするオブジェクトインストール・スクリプトとして、それらを作成するスクリプトが登録されています。