0 フォロワー · 5 投稿

ITのセキュリティとは、ハードウェア、ソフトウェアまたは情報の盗難および損傷からコンピューターシステムを保護すること、およびこれらが与えるサービスの混乱または誤った指示から保護することです。

InterSystemsデータプラットフォームドキュメントでの認証

記事 Toshihiko Minamoto · 5月 14, 2024 8m read

「30 秒も経ってるのにサービスを受けられないなんて、 あり得ない! もう結構!」

「大変申し訳ございません。 次回からはご予約なさってはいかがでしょうか。」

お気に入りのレストランでこんなコメントを聞いたら、そんな発言はばかばかしいと思うのではないでしょうか。 でも、API のコンテキストでは、まったく合理的な意見です。 お気に入りのレストランと同じように、API にも常連客がいます。よく訪問するユーザーのことです。 同じように繰り返し予約が可能であればよいと思いませんか?

これには、IRIS の基本機能がいくつか関わってきます。 まず、%SYSTEM.License インターフェースを理解する必要があります。 これは、IRIS インスタンスのライセンス使用状況に関する情報を取得するために提供されているインターフェースです。 次に、%CSP.SessionEvents クラスについて学ぶ必要があります。 このクラスを使うと、CSP セッションのライフサイクル全体で呼び出される様々なメソッドをオーバーライドできます。

まずは、%SYSTEM.License インターフェースから見ていきましょう。 このクラスには、$SYSTEM.License を使って呼び出せるメソッドもあれば、##class(%SYSTEM.License) で呼び出す必要のあるメソッドもあります。 一貫性を維持するためにも、ここでは、すべてのメソッドに使用できる ##class(%SYSTEM.License) を使用しましょう。 このユースケースでは、使用できるライセンス数と特定のユーザーが使用しているライセンス数をチェックできる必要があります。 

まず、使用できるライセンス数を確認しましょう。 少なくとも 1 つのライセンスが使用可能である場合、予約には十分であるため、それ以上に確認する必要はありません。 また、ログインしようとしているユーザーが予約を保持しているユーザーであるかを確認する必要もあります。 これは、以下のように、LUAvailable メソッドへの単純な呼び出しで実現できます。

if (##class(%SYSTEM.License).LUAvailable()) || ($USERNAME = username) > 0{    return $$$OK}

次に、予約済みのユーザーが現在ログイン中であるかを確認する必要があります。 すでにレストランで着席している場合は、使用できる席があるかどうかを確認する必要はありません。 ユーザーが常に特定のアドレスから接続する場合は、LicenseCount メソッドを使用してそれを確認することができます。 ユーザー識別子を引数として取り、そのユーザーがその時点でライセンスを使用しているかどうかを示す整数を返します。 例:

set licenseusage = ##class(%SYSTEM.License).LicenseCount("David@127.0.0.1")

このユーザーがライセンスを使用しているかどうかがわかります。 0 を返す場合、そのユーザーはライセンスを使用していませんが、1 を返す場合はライセンスを使用していることになります。 このメソッドが 1 よりも大きい数値を返す場合、ユーザーが最大接続数を超過しており、接続ごとに 1 つのライセンスを使用していることを意味するため、注意が必要です。 その場合は、別の問題を新たに修正する必要があります!

どのアドレスから接続されているのかがわからない場合は、この状況に別の方法で対応して、いくつかの追加ステップが必要となります。 ここでは、ConnectionList クラスのクエリを使用しましょう。 次のコードを検討してください。

ClassMethod CheckReservation(username As %String) As %Status{    try{        if (##class(%SYSTEM.License).LUAvailable() > 1) || ($USERNAME = username) {            return $$$OK        }        set rs = ##class(%ResultSet).%New("%SYSTEM.License:ConnectionList")        set sc = rs.%Execute()        if $$$ISERR(sc) {$$$ThrowStatus(sc)}        while rs.%Next(){            if $P(rs.%GetData(1),"@",1) = username{                return $$$OK            }        }        $$$ThrowStatus($$$ERROR(5001,"Reserved User Not Logged In."))    }    catch ex{        do ex.Log()        return ex.AsStatus()    }}

このメソッドでは、ユーザー名に基づいて 1 つのライセンスのみを予約できることに注意してください。 ユースケースで 2 つ以上のライセンスを確保する必要がある場合は、ConnectionList クエリの結果を確認し、何人の顧客がログイン済みであり、席を待つ間にバーで最初のグラスを傾けているかを数える必要があります。 次に、残りの LUAvailable が単に 2 以上であるだけでなく、すべての予約に対応できるかを確認する必要があります。

このメソッドは通常、%ResultSet オブジェクトを使用して、%SYSTEM.License クラスから LicenseCount クエリにアクセスします。 一般に %SQL.Statement を使用する方が好ましくはありますが、%PrepareClassQuery は必ずこのクエリで失敗します。 そのため、ここではこれを使用することはできません。 このクエリは各接続に対して行を返すため、その後で結果セットを反復しましょう。 各行に対し、最初の列には、ユーザー名と IP アドレスを@ で区切ったライセンス ID が含まれます。 探しているのは(ユーザー名と IP アドレスの組み合わせではなく)ユーザー名のみであるため、その列の最初の部分を提供されたユーザー名と比較して、一致する場合は成功ステータスを返します。 ただし、結果セット全体にユーザー名が見つからない場合は、エラーを返します。

このメソッドを、代わりにロールをチェックするように変更したい場合(スーパーユーザーがいつでもシステムにアクセスできるように、%All ロールを持つ少なくとも 1 人のユーザーがログインできるようにするなど)、%SYS ネームスペースに切り替えて、ループを反復する過程で各ユーザーのロールを検証することで実現できます。 ##class(Security.Users).Get($P(rs.%GetData(1),”@”,1),.props) を使用してから、props(“Roles”) [ “%All” を使用して、ロールが props(“Roles”) に含まれているかを確認できます。 正しいアプリケーションネームスペースにログインできるように、最終的には元のネームスペースに忘れずに戻るようにしましょう。 すると、コードは以下のようになります。

ClassMethod CheckReservation(role As %String) As %Status{    try{        set returnns = $NAMESPACE        zn "%SYS"        set sc = ##class(Security.Users).Get($USERNAME,.props)        if (##class(%SYSTEM.License).LUAvailable() > 1) || (props("Roles") [ role) {            zn returnns            return $$$OK        }        set rs = ##class(%ResultSet).%New("%SYSTEM.License:ConnectionList")        set sc = rs.%Execute()        if $$$ISERR(sc) {$$$ThrowStatus(sc)}        while rs.%Next(){            set sc = ##class(Security.Users).Get($P(rs.%GetData(1),"@",1),.props)            if props("Roles") [ role{                zn returnns                return $$$OK            }        }        zn returnns        $$$ThrowStatus($$$ERROR(5001,"Reserved User Not Logged In."))    }    catch ex{        do ex.Log()        return ex.AsStatus()    }}

License API 内には、使用できる実用的なクラスクエリが他にもあります。 特定のアプリケーションへのログインを検証する場合は、ConnectionAppList の方が適しているかもしれません。 ライセンスのタイプ(User、CSP、Mixed、または Grace)を検査する場合は、UserList クラスクエリを使用すると良いでしょう。 ProcessList クエリを操作すると、ユーザーが実行するプロセスの詳細をさらに詳しく把握することができます。 ただし、あまりにも夢中になりすぎた場合は、ログインプロセスにゆっくりと取り組むようにしましょう。

この部分のコードが完成したので、どこに配置するかを決めなければなりません。 ただし、きちんと配置するには、%CSP.SessionEvents クラスの基本を知っておく必要があります。 このクラスには、CSP セッションが開始、終了、またはタイムアウトになった場合やユーザーがログインまたはログアウトした場合の動作を制御するためにオーバーライドできるメソッドがいくつか含まれています。 ここでは、セッションに割り当てられたライセンスの前に実行する OnStartSession() メソッドに特に注目できます。 そこでメソッドを実行し、エラーを返した場合には、%session.EndSession を 1 にセットするだけで完了です。 すると、ユーザーには標準のライセンス数不足メッセージが表示され、ログインできないようにすることができます。

%CSP.SessionEvents 拡張機能と以前に定義したクラスメソッドを使って、User.SessionEvents というメソッドを作成します。 次に、その中の 1 つのメソッドのみをオーバーライドし、他の予約ようにスペースを空けておくようにします。 すると、そのメソッドは次のようになります。

ClassMethod OnStartSession(){    if $$$ISERR(##class(User.SessionEvents).CheckReservation("David")){        set %session.EndSession=1    }    Quit $$$OK}

ネームスペースが複数ある場合は、このクラスを使用するすべての CSP アプリケーションにクラスを作成するようにしてください。

最後に、アプリケーションがこれらのセッションを使用できるように構成します。 これは、管理ポータルで、システム管理 > セキュリティ > アプリケーション > Web アプリケーションに移動して行えます。 カスタムセッションイベントクラスを使用するアプリケーションをクリックし、 次に、「イベントクラス」ボックスに User.SessionEvents を入力します。 それ以降、ログインするたびに、カスタマイズされた OnStartSession メソッドが呼び出され、予約の余地がない場合には他のユーザーがログインできなくなります。

ここには、検討すべき重要な実装の詳細が 1 つあります。 これらのセッションイベントをシステム管理ポータルに適用すべきかどうかです。 適用する場合は、何らかの問題が生じても、ログインして修正できない状況に陥る可能性があります。 適用しない場合は、誰かがログインし、予約しようとしていたライセンスを使用してしまう可能性があります。 おそらく、そのユーザーに予約チェックを迂回できる特定のロールがあるかを確認するようにコードを編集することができますが、 これについては、あなた次第です。

今後は、常連客が定期的に訪れるたびに、席に座れることを保証できます!

0
1 100
記事 Toshihiko Minamoto · 3月 17, 2022 5m read


この記事では、以下のオンラインデモを通じ、GitHub アカウントを使用した OAuth2 認証の基本を説明します。
https://dappsecurity.demo.community.intersystems.com/csp/user/index.csp(SuperUser | SYS)

推奨事項:

目的を達成するために必要な 3 つのステップ:

  • ステップ 1: GitHub 認証サーバーにアプリケーションを登録する
  • ステップ 2: InterSystems 管理ポータルから OAuth 2.0 クライアントを構成する
  • ステップ 3: GitHub アカウントでログインするための API を呼び出す

それでは始めましょう。 

ステップ 1: GitHub 認証サーバーにアプリケーションを登録する

GitHub 認証サーバーにアプリケーションを登録するには、GitHub アカウントが必要です。 
GitHub アカウントにログインして https://github.com/settings/developers に移動し、[OAuth Apps(OAuth アプリ)]タブで[New OAuth App(新しい OAuth アプリ)]ボタンをクリックします。

 

アプリケーション名、ホームページ URL、説明、および認証コールバック URL を入力します。
認証コールバック URL は OAuth2.Response.cls クラス({domain}/csp/sys/oauth2/OAuth2.Response.cls)を参照する必要があることに注意してください。
[Register Application(アプリケーションを登録)]をクリックします。

すると、詳細ページが開きます。 [Generate a new client secret(新しいクライアントシークレットを生成)]をクリックし、IRIS OAuth2 クライアントを構成する際に使用するクライアント ID と秘密鍵を保存します。
アプリケーションが登録されました。

ステップ 2: InterSystems 管理ポータルから OAuth 2.0 クライアントを構成する

システム]>[セキュリティ管理]>[OAuth 2.0 クライアント]に移動し、[サーバーの説明を作成]ボタンをクリックします。

上部にある[手動]ボタンをクリックし、以下の詳細を入力してサーバーの説明を保存します。

システム]>[セキュリティ管理]>[OAuth 2.0 クライアント]に戻り、[クライアント構成]をクリックします。

[一般]タブに詳細を入力します。 クライアントリダイレクト URL は、アプリケーションを GitHub に登録した際に入力した認証コールバック URLと同じであることに注意してください。

[クライアント資格情報]に、アプリケーションを GitHub に登録した際に生成したクライアント ID とクライアントシークレットを入力し、クライアント構成を保存します。

ステップ 3: GitHub アカウントでログインするための API を呼び出す

オンラインデモ(https://dappsecurity.demo.community.intersystems.com/csp/user/index.csp)に移動し、 SuperUser | SYS でログインします。
トップメニューから[Login with GitHub(GitHub でログイン)]をクリックします。


これで、Oauth.cls ページが開きます。 サインインを試す前に、必ず GitHub からログアウトしてください。 [GitHub Sign In(GitHub サインイン)]をクリックします。

システムは %SYS.OAuth2.Authorization クラス(https://docs.intersystems.com/latest/csp/documatic/%25CSP.Documatic.cls?&LIBRARY=%25SYS&PRIVATE=1&CLASSNAME=%25SYS.OAuth2.Authorization)の GetAuthorizationCodeEndpoint メソッドを使用して、GitHub 認証サーバーに移動します。

ログインに成功すると、システムは OauthRe.cls ページにリダイレクトします。

コードは https://github.com/mwaseem75/Data_APP_Security のリポジトリから入手できます。

以上です!

0
0 374
記事 Toshihiko Minamoto · 12月 7, 2020 10m read

    以前の記事では Arduino を使い始め、最終的には気象観測所のデータを表示できるようになりました。 この記事ではさらに掘り下げ、InterSystems Caché アプリケーションに対して RFID カードと Arduino を介した認証をセットアップします。

 

認証の委任

Caché には認証コードの書き込みを許可することで、認証を委任するための仕組みがあります。 この仕組みを有効にするには、次の手順を実行する必要があります。

  • ZAUTHENTICATE ルーチンにユーザー認証コードを記述します。 このルーチンにはユーザー名/パスワードの取得、それらの検証と権限の割り当て、パスワード変更、トークン生成の 4 つのエントリポイントがあります。 詳細については、以下をお読みください。
    1. Caché で委任認証を有効にします([SMP] → [System Administration] → [Security] → [System Security] → [Authentication/CSP Session Options] を開き、[Allow Delegated authentication] ボックスにチェックを入れて設定を保存します)。
    2. 関連するサービスかアプリケーションの委任認証を有効にします(前者の場合は [SMP] → [Menu] → [Manage Services] → [Service] → [Allowed Authentication Methods] → [Delegated] を選択 → [Save]、後者の場合は [SMP] → [Menu] → [Manage Web Applications] → [Application] → [Allowed Authentication Methods] → [Delegated] を選択 → [Save])。

    仕組み

    委任認証は、委任認証が有効になっているサービスや Web アプリケーションに対してユーザーが認証される際に発生します。

    1. ZAUTHENTICATE ルーチンが呼び出されます。 このルーチンのコードはユーザーによって書かれたものであり、OS への呼び出しを含む任意の Caché ObjectScript コードである可能性があります。
    2. 次のステップは、ZAUTHENTICATE の呼び出しが成功したかどうかによって決まります。
    • ZAUTHENTICATE の呼び出しが成功し、ユーザーが ZAUTHENTICATE で認証されたのが初めてだった場合は「委任されたユーザー」が作成されます。 ZAUTHENTICATE がロールやその他の権限をユーザーに割り当てた場合は、それらがユーザープロパティになります。
    • ZAUTHENTICATE の呼び出しが成功し、ユーザーが ZAUTHENTICATE で認証されたのが初めてではなかった場合はそのユーザーのレコードが更新されます。
    • ZAUTHENTICATE の呼び出しが成功しなかった場合、ユーザーはアクセスエラーを受け取ります。
    1. インスタンスとサービスで 2 要素認証が有効になっている場合は、ユーザーの電話番号と事業者の検索が開始されます。 これらの情報が入力されている場合は、2 要素認証が実行されます。 入力されていない場合は、ユーザーは認証されません。

    ユーザー情報の出処は?

    次のように、アプリケーション/サービスで有効になっている認証方法に応じた 2 つの認証方法があります。

    • 委任: ユーザー名/パスワードは ZAUTHENTICATE ルーチン(GetCredentials エントリポイント)から取得され、ZAUTHENTICATE を使用して検証されます(ユーザータイプ: 委任)。
  • 委任およびパスワード: ユーザー名/パスワードは GetCredentials から取得されますが、標準の Caché ツールを使用してチェックされます(ユーザータイプ: Caché)。
  • 次に、ZAUTHENTICATE ルーチンとそのエントリポイントを見てみましょう。

    ZAUTHENTICATE

    これはメインルーチンであり、次の 4 つのエントリポイントで構成されています。

    GetCredentials

    このエントリポイントはサービスで委任認証が有効になっている場合に呼び出され、ユーザーにユーザー名/パスワードの入力を求める代わりに呼び出されます。 このルーチンのコードは、ユーザー名とパスワードを(何らかの方法で)取得します。 その後、(このエントリポイントの外部で)受信したユーザー名とパスワードはユーザーから通常の方法で入力されたかのように認証されます。 ユーザー名とパスワードは、キーボードからの入力、API、外部デバイスを使用したスキャンなど、任意の方法で取得できます。 この記事では、RFID カードを使用した認証を実装します。

    このエントリポイントはステータスを返します。ステータスがエラーの場合は監査ログに記録され、認証試行は拒否されます。 ただし、エラーステータス $SYSTEM.Status.Error($$$GetCredentialsFailed) が返された場合は、例外的に通常のユーザー名/パスワードの入力が続きます。 シグネチャは次のとおりです。

    GetCredentials(ServiceName, Namespace, Username, Password, Credentials) Public { }

    説明:

    • ServiceName – 接続が確立されるサービスの名前
    • Namespace – ネームスペース(接続時に指定されている場合)
    • Username – ユーザー名
    • Password – パスワード
  • Credentials – 現在使用されていません
  • このエントリポイントの重要な機能について説明します。 委任認証とパスワード認証の両方がサービスやアプリケーションで有効になっている場合、ユーザー名とパスワードは GetCredentials エントリポイントを介して受信されますが、それらの情報は標準のパスワード認証に使用されます(ユーザーが手動で入力した場合と同じ)。また、認証が成功した場合のユーザーは委任ユーザーではなく通常の Cache ユーザーになります。

    ZAUTHENTICATE

    初回認証が成功すると、ZAUTHENTICATE はロールやその他のユーザープロパティを定義します。 初回認証以外の場合はプロパティが更新されます(例えば、Roles はログインのたびに指定する必要があります)。 そのために、Properties 配列のプロパティを定型化したコードで設定する必要があります。 シグネチャは以下のとおりです。

    ZAUTHENTICATE(ServiceName, Namespace, Username, Password, Credentials, Properties) Public { }

    Properties 配列の説明:

    • Properties("Comment") — コメント
    • Properties("FullName") — 氏名
    • Properties("NameSpace") — 初期ネームスペース
    • Properties("Roles") — カンマ区切りのロールのリスト
    • Properties("Routine") — 初期ルーチン
    • Properties("Password") — パスワード
    • Properties("Username") — ユーザー名
    • Properties("PhoneNumber") — ユーザーの電話番号
    • Properties("PhoneProvider") — 電話会社
  • Properties("AutheEnabled") — 標準の 2 要素認証を有効化します(この目的のために、$$$AutheTwoFactorSMS に等しい値を設定する必要があります)
  • ChangePassword

    ユーザーパスワードを変更するためのエントリポイントです。シグネチャは次のとおりです。

    ChangePassword(Username, NewPassword, OldPassword, Status) Public { }

    説明:

    • NewPassword — 新しいパスワード
    • OldPassword — 古いパスワード
  • Status — パスワード変更の結果
  • SendTwoFactorToken

    標準の 2 要素認証で使用されるものです。 リクエストの形式と認証トークンを指定します。 シグネチャは以下のとおりです。

    SendTwoFactorToken(Username, ServiceName,Namespace,Application,Credentials,SecurityToken,TwoFactorTimeout,UserPhoneNumber) Public { }
    

    説明:

    • Application — ユーザーが接続している CSP アプリケーションまたはルーチン
    • SecurityToken — ユーザーに送信されるトークン
    • TwoFactorTimeout — トークンの有効期限が切れる時間
  • UserPhoneNumber — ユーザーの電話番号
  • まずは簡単な例から始めましょう。Windows での Caché ターミナルを担う %Service_Console サービスは、ユーザー名とパスワードの入力をユーザーに要求します。 このサービスに対して委任認証を有効にしましょう。 以下は、ユーザーにユーザー名とパスワードの入力を要求する ZAUTHENTICATE ルーチン(%SYS ネームスペース内)です。

    ZAUTHENTICATE(ServiceName, Namespace, Username, Password, Credentials, Properties) PUBLIC {
        #Include %occStatus
        Quit $$$OK
    }
    GetCredentials(ServiceName, Namespace, Username, Password, Credentials) Public {
        #Include %occErrors
        #Include %occStatus
        Do ##class(%Prompt).GetString("USER:",.Username)
        Do ##class(%Prompt).GetString("PASS:",.Password)
        Quit $$$OK
    }

    ターミナルの場合、これは通常のユーザー名認証と同じように見えます。

    >USER: _SYSTEM
    >PASS: SYS

    RFID

    それでは、RFID による認証を見てみましょう。 考え方は単純で、Caché が暗号化されたユーザー名とパスワードをカードに書き込み、認証中に Caché がカードをスキャンして復号化し、受け取ったユーザー名とパスワードを認証に使用するというものです。

    まず、Arduino Uno と RFID-RC522 モジュールの回路図をご覧ください。

    MF522 ライブラリを使用した C のコードはここにあります。 このコードでは、COM ポート経由で次の 2 つのコマンドを受信できます。

    • Get – RFID カードのブロック 2 / 4 / 5 / 6 の内容が COM ポートに渡されます
  • Set@bloc2@bloc4@bloc5@bloc6 — ブロック 2 / 4 / 5 / 6 の値が受信したデータに置き換えられます
  • Caché 側には Arduino.Delegate クラスがあり、その中に次の 2 つの対応するエントリポイントがあります。

    • SetCredentials — ユーザー名とパスワードの入力を取得し、それをシステムに格納されているキーを使用して AES 暗号化で暗号化し、RFID カードに書き込みます。
  • GetCredentials — カードから暗号化テキストを受信して復号化し、ユーザー名、パスワード、および操作のステータスを返します。
  • また、GetCredentials を使用して Arduino.Delegated クラスを呼び出す ZAUTHENTICATE ルーチンは以下のとおりです。

    ZAUTHENTICATE(ServiceName, Namespace, Username, Password, Credentials, Properties) PUBLIC {
        #Include %occStatus
        Quit $$$OK
    }
    GetCredentials(ServiceName, Namespace, Username, Password, Credentials) Public {
        #Include %occErrors
        #Include %occStatus
        Quit ##class(Arduino.Delegated).GetCredentials(.Username, .Password)
    }
    

    これで準備完了です! 組み立て後のデバイスは次のようになります。

    次のようにターミナルでシステム暗号化キーを設定します(%SYS ネームスペースと Arduino.Delegated クラスを使用できる必要があります)。

    Do ##class(Arduino.Delegated).InitEncryption(Key, IV)
    

    ここで、Key は暗号化キー、IV は初期化ベクトルです。 これらは、ユーザー名とパスワードを暗号化するために使用されます。 コマンドを使用して認証するには、Arduino を Caché に接続し、カードに情報を書き込みます。

    Do ##class(Arduino.Delegated).SetCredentials("_SYSTEM", "SYS")
    

    適切なサービスまたは Web アプリケーション(端末やシステム管理ポータルなど)で委任認証とパスワード認証を有効にすると、カードを RFID カードリーダーにかざすことで認証できるようになります。

    考えられる機能強化

    • マネージド暗号化キーを使用してユーザー名とパスワードを暗号化すると、セキュリティを強化できます。
    • 2 要素認証を使用すると、セキュリティを強化できます。具体的には、先にユーザー名とパスワードのペアを取得してから、ユーザーに固有のキーが格納されているカードを読み取ります。 次に、受信したキーをシステムに格納されている特定ユーザーのキーで確認する必要があります。 任意のユーザーデータを格納する方法は、InterSystems のコミュニティで議論されています
  • それぞれ 15 文字を超えるユーザー名とパスワードを格納する機能を追加します。
  • まとめ

    柔軟性の高い Caché の認証システムを使えば、任意のユーザー認証ロジックを実装できます。

    リンク

    0
    0 1034
    記事 Shintaro Kaminaka · 8月 26, 2020 23m read

    作成者:Daniel Kutac(InterSystems セールスエンジニア)

    パート 3. 付録

    InterSystems IRIS OAUTH クラスの説明

    この連載の前のパートでは、InterSystems IRIS を OAUTH クライアントおよび認可/認証サーバー(OpenID Connect を使用)として機能するように構成する方法について学びました。 この連載の最後のパートでは、InterSystems IRIS OAuth 2.0 フレームワークを実装するクラスについて説明します。 また、一部の API クラスのメソッドの使用例についても説明します。

    OAuth 2.0 を実装する API クラスは、目的に応じて 3 種類のグループに分けることができます。 すべてのクラスは %SYS ネームスペースで実装されています。 これらの一部は(% package 経由で)公開されていますが、一部は非公開になっており、開発者が直接呼び出すことはできません。

    内部クラス

    これらのクラスは OAuth2 パッケージに属しています。

    次の表に、対象となるクラスの一部を掲載しています(クラスの完全なリストについては、CachéあるいはIRIS インスタンスのオンラインクラスリファレンスを参照してください)。 以下の表に掲載されているものを除き、これらのクラスをアプリケーション開発者が直接使用することはできません。

      <td>
        説明
      </td>
    </tr>
    
    <tr style="height:0px">
      <td>
        OAuth2.AccessToken
      </td>
      
      <td>
        Persistent(永続クラス)
    

    OAuth2.AccessToken は、OAuth 2.0 アクセストークンとその関連情報を格納します。 これは、アクセストークンの OAUTH クライアントコピーです。OAuth2.AccessToken は、SessionId と ApplicationName の組み合わせによってインデックス化されます。 したがって、SessionId/ApplicationName ごとに 1 つのスコープのみをリクエストできます。 2 回目のリクエストが別のスコープで行われ、アクセストークンがまだ付与されている場合は、新しいリクエストのスコープが予期されるスコープになります。

    <tr style="height:0px">
      <td>
        OAuth2.Client
      </td>
      
      <td>
        Persistent(永続クラス)
    

    OAuth2.Application クラスは OAuth2 クライアントを記述し、RFC 6749 に基づいてアプリケーションを認可するために使用する認可サーバーを参照します。 クライアントシステムは、さまざまなアプリケーションで複数の認可サーバーと共に使用できます。

    <tr style="height:0px">
      <td>
        OAuth2.Response
      </td>
      
      <td>
        CSPページ
    

    これは、InterSystems IRIS OAuth 2.0 クライアントコードから使用される OAuth 2.0 認可サーバーからの応答用のランディングページです。 応答はここで処理され、最終的なターゲットにリダイレクトされます。

    <tr style="height:0px">
      <td>
        OAuth2.ServerDefinition
      </td>
      
      <td>
        Persistent(永続クラス)
    

    OAUTH クライアント(この InterSystems IRIS インスタンス)が使用する認可サーバーの情報が格納されています。 認可サーバーの定義ごとに複数のクライアント構成を定義できます。

    <tr style="height:0px">
      <td>
        OAuth2.Server.AccessToken
      </td>
      
      <td>
        Persistent(永続クラス)
    

    アクセストークンは OAUTH サーバーの OAuth2.Server.AccessToken によって管理されます。 このクラスには、アクセストークンと関連プロパティが格納されます。 このクラスは、認可サーバーのさまざまな要素間の通信手段でもあります。

    <tr style="height:0px">
      <td>
        OAuth2.Server.Auth
      </td>
      
      <td>
        CSP ページ
    

    認可サーバーは、RFC 6749 で指定されている認可コードおよびインプリシットグラントタイプの認可制御フローをサポートします。 OAuth2.Server.Auth クラスは認可エンドポイントとして機能し、RFC 6749 に従ってフローを制御する %CSP.Page のサブクラスです。

    <tr style="height:0px">
      <td>
        OAuth2.Server.Client
      </td>
      
      <td>
        Persistent(永続クラス)
    

    OAuth2.Server.Configuration は、この認可サーバーに登録したクライアントを記述する永続クラスです。

    <tr style="height:0px">
      <td>
        OAuth2.Server.Configuration
      </td>
      
      <td>
        Persistent(永続クラス)
    

    認可サーバーの構成が格納されます。 すべての構成クラスには、ユーザーが構成の詳細を入力するためのシステム管理ポータルページがそれぞれ存在します。

    クラス名

    OAuth2.Client、OAuth2.ServerDefinition、OAuth2.Server.Client、OAuth2.Configuration の各オブジェクトは UI を使用せずに開き、変更し、作成または変更した構成を保存できます。 これらのクラスを使用し、構成をプログラムで操作できます。

    サーバーカスタマイズ用クラス

    これらのクラスは %OAuth2 パッケージに属しています。 このパッケージには、一連の内部クラス(ユーティリティ)が含まれています。ここでは、開発者が使用できるクラスについてのみ説明します。 これらのクラスは、OAuth 2.0 Server Configuration(サーバー構成)ページで参照されます。

      <td>
        CSPページ
    

    %OAuth2.Server.Authenticate はデフォルトの Authenticate クラスだけでなく、ユーザーが作成したすべての Authenticate クラスのサブクラスとして機能します。 Authenticate クラスは、ユーザーを認証するために OAuth2.Server.Auth の認可エンドポイントによって使用されます。 このクラスを使用すると、認証プロセスをカスタマイズできます。次のメソッドを OAuth2.Server のデフォルトをオーバーライドするために実装できます。 DirectLogin– ログインページを表示しない場合にのみ使用します。 DisplayLogin – 認可サーバーのログインフォームを実装します。 DisplayPermissions – リクエストされたスコープのリストを使用してフォームを実装します。CSS を変更することで、外観や操作性をさらにカスタマイズできます。 CSS スタイルは DrawStyle メソッドで定義されます。loginForm は DisplayLogin フォーム用です。permissionForm は DisplayPermissions フォーム用です。

    <tr style="height:0px">
      <td>
        %OAuth2.Server.Validate
      </td>
      
      <td>
        CSP ページ
    

    これは、サーバーに含まれているデフォルトの Validate User Class(ユーザー検証クラス)です。 デフォルトのクラスは、認証サーバーが配置されている Cache またはIRISインスタンスのユーザーデータベースを使用してユーザーを検証します。 サポートされるプロパティは、issuer(Issuer)、role、sub(Username)です。Validate User Class は Authorization Server Configuration(認可サーバーの構成)で指定されます。 ユーザー名とパスワードの組み合わせを検証し、ユーザーに関連付けられたプロパティ一式を返す ValidateUser メソッドを含める必要があります。

    <tr style="height:0px">
      <td>
        %OAuth2.Server.Generate
      </td>
      
      <td>
        Registered Object(登録オブジェクト)
    

    %OAuth2.Server.Generate は、サーバーに含まれているデフォルトのGenerate Token Class(トークン生成クラス)です。 デフォルトのクラスは、ランダムな文字列を opaque アクセストークンとして生成します。Generate Token Class は、Authorization Server Configuration で指定されます。 ValidateUser メソッドによって返されるプロパティの配列に基づいてアクセストークンを生成するために使用される GenerateAccessToken メソッドを含める必要があります。

    <tr style="height:0px">
      <td>
        %OAuth2.Server.JWT
      </td>
      
      <td>
        Registered Object(登録オブジェクト)
    

    %OAuth2.Server.JWT は、サーバーに含まれている JSON Web トークンを作成する Generate Token Class です。 Generate Token Class は、Authorization Server Configuration で指定されます。 ValidateUser メソッドによって返されるプロパティの配列に基づいてアクセストークンを生成するために使用される GenerateAccessToken メソッドを含める必要があります。

    <tr style="height:0px">
      <td>
        %OAuth2.Utils
      </td>
      
      <td>
        Registered Object(登録オブジェクト)
    

    このクラスは、さまざまなエンティティのログの記録を実装します。 カスタマイズの章のサンプルコードに、可能な使用法を示しています。

    %OAuth2.Server.Authenticate

    次の画像に、OAuth 2.0 認可サーバー構成の対応するセクションを示します。

    OpenID Connect を JWT 形式の識別情報トークン(id_token)と共に使用する場合は、構成内のデフォルトの Generate Token Class である %OAuth2.Server.Generate%OAuth2.Server.JWT に置換してください。それ以外の場合は、デフォルトの Generate クラスのままにしてください。

    カスタマイズオプションについては、後で別の章で詳しく説明します。

    公開 API クラス

    公開 API クラスは、アプリケーション開発者が Web アプリケーションのメッセージフローに正しい値を渡し、アクセストークンの検証やイントロスペクションなどを実行するために使用されます。

    これらのクラスは %SYS.OAuth2 パッケージで実装されています。 次の表に、実装されているクラスの一部を掲載しています。

      <td>
        Registered Object(登録オブジェクト)
    

    %SYS.OAuth2.AccessToken クラスは、リソースサーバーへの認証にアクセストークンを使用できるようにするクライアント操作を定義します。基本となるトークンは、CACHESYS データベースの OAuth2.AccessToken に格納されます。 OAuth2.AccessToken は、SessionId と ApplicationName の組み合わせによってインデックス化されます。 したがって、SessionId/ApplicationName ごとに 1 つのスコープのみをリクエストできます。 2 回目のリクエストが別のスコープで行われ、アクセストークンがまだ付与されている場合は、新しいリクエストのスコープが予期されるスコープになります。

    <tr style="height:0px">
      <td>
        %SYS.OAuth2.Authorization
      </td>
      
      <td>
        Registered Object(登録オブジェクト)
    

    %SYS.OAuth2.Authorization クラスには、アクセストークンを取得してクライアントを認可するために使用される操作が含まれています。基本となるトークンは、CACHESYS データベースの OAuth2.AccessToken に格納されます。 OAuth2.AccessToken は、SessionId と ApplicationName の組み合わせによってインデックス化されます。 したがって、SessionId/ApplicationName ごとに 1 つのスコープのみをリクエストできます。 2 回目のリクエストが別のスコープで行われ、アクセストークンがまだ付与されている場合は、新しいリクエストのスコープが予期されるスコープになります。このクラスは CACHELIB にあるため、どこでも使用できることに注意してください。 ただし、トークンストレージは CACHESYS にあるため、ほとんどのコードでは直接使用できません。

    <tr style="height:0px">
      <td>
        %SYS.OAuth2.Validation
      </td>
      
      <td>
        Registered Object(登録オブジェクト)
    

    %SYS.OAuth2.Validation クラスは、アクセストークンの検証(または無効化)に使用されるメソッドを定義します。 基本となるトークンは、CACHESYS データベースの OAuth2.AccessToken に格納されます。 OAuth2.AccessToken は、SessionId と ApplicationName の組み合わせによってインデックス化されます。 したがって、SessionId/ApplicationName ごとに 1 つのスコープのみをリクエストできます。 2 回目のリクエストが別のスコープで行われ、アクセストークンがまだ付与されている場合は、新しいリクエストのスコープが予期されるスコープになります。

    %SYS.OAuth2.AccessToken

    このグループのいくつかのメソッドとクラスを詳しく見てみましょう。

    アクセストークンを使用するすべてのクライアントアプリケーションクラスは、その有効性をチェックする必要があります。 このチェックは、OnPage メソッド(または ZEN か ZENMojo ページの対応するメソッド)のどこかで実行されます。

    こちらがそのコードスニペットです。

     // OAuth2 サーバーからのアクセストークンがあるかどうかをチェックします。
     set isAuthorized=##class(%SYS.OAuth2.AccessToken).IsAuthorized(..#OAUTH2APPNAME,,"scope1,
         scope2",.accessToken,.idtoken,.responseProperties,.error)
    
     // アクセストークンがあるかどうかをさらにチェックします。
     // 以下は実行可能なすべてのテストであり、あらゆるケースで必要なわけではありません。
     // 各テストで返される JSON オブジェクトが単に表示されています。
     if isAuthorized {
        // 何らかの処理を実行します – リソースサーバーの API を呼び出して目的のデータを取得します。
     }
    

    リソースサーバーの API を呼び出すたびに、アクセストークンを提供する必要があります。 この処理は、%SYS.OAuth2.AccessToken メソッドの AddAccessToken メソッドによって実行されます。こちらがそのコードスニペットです。

     set httpRequest=##class(%Net.HttpRequest).%New()
      // AddAccessToken は現在のアクセストークンをリクエストに追加します。
      set sc=##class(%SYS.OAuth2.AccessToken).AddAccessToken(
        httpRequest,,
        ..#SSLCONFIG,
        ..#OAUTH2APPNAME)
     if $$$ISOK(sc) {
        set sc=httpRequest.Get(.. Service API url …)
     }
    

    この連載の前のパートで提供したサンプルコードでは、最初のアプリケーションページ(Cache1N)の OnPreHTTP メソッドでこのコードを確認することができました。 このコードは、アプリケーションの最初のページでアクセストークンチェックを実行するのに最適な場所です。

    ClassMethod OnPreHTTP() As %Boolean [ ServerOnly = 1 ]
    {
     set scope="openid profile scope1 scope2"
        #dim %response as %CSP.Response
     if ##class(%SYS.OAuth2.AccessToken).IsAuthorized(..#OAUTH2APPNAME,,
        scope,.accessToken,.idtoken,.responseProperties,.error) {
          set %response.ServerSideRedirect="Web.OAUTH2.Cache2N.cls"
     }
     quit 1
    }

    上記のコードにある SYS.OAuth2.AccessToken クラスの IsAuthorized メソッドは有効なアクセストークンが存在するかどうかをチェックし、存在しない場合に認可サーバーの認証フォームを指すログインボタン/リンクを使用してページの内容を表示し、存在する場合に実際にデータ取得処理を実行する 2 番目のページにリダイレクトします。

    ただし、このコードは次のように変更できます。

    ClassMethod OnPreHTTP() As %Boolean [ ServerOnly = 1 ]
    {
     set scope="openid profile scope1 scope2"
     set sc=##class(%SYS.OAuth2.Authorization).GetAccessTokenAuthorizationCode(
        ..#OAUTH2APPNAME,scope,..#OAUTH2CLIENTREDIRECTURI,.properties)
     quit +sc
    }

    この場合は、結果が異なります。 %SYS.OAuth2.Authorization クラスの GetAccessTokenAuthorizationCode メソッドを使用すると、アプリケーションの最初のページの内容を表示せずに、認可サーバーの認証フォームに直接移動します。

    これは、Web アプリケーションがモバイルデバイスのネイティブアプリケーションから呼び出され、一部のユーザー情報がネイティブアプリケーション(ランチャー)によってすでに表示されており、認可サーバーを指すボタンを含む Web ページを表示する必要がない場合に便利です。

    署名付き JWT トークンを使用する場合は、その内容を検証する必要があります。 この検証は次のメソッドで実行されます。

     set valid=##class(%SYS.OAuth2.Validation).ValidateJWT(applicationName,accessToken,scope,,.jsonObject,.securityParameters,.sc)
    

    メソッドパラメーターの詳細については、Class Reference ドキュメントをご覧ください。

    カスタマイズ

    OAUTH が認証 / 承認 UI のカスタマイズ用に提供しているオプションについてもう少し説明します。

    勤務先のポリシーで、スコープの付与に関してより限定的な動作が要求されているとします。 たとえば、取引先銀行内のさまざまな金融システムに接続するホームバンキングアプリケーションを実行できるとしましょう。 銀行は、取得対象の実際の銀行口座に関する情報を含むスコープへのアクセスのみを許可します。 銀行は非常に多くの口座を運営しているため、すべての口座に静的なスコープを定義することは不可能です。 代わりに、認可処理中にその場でスコープを生成する処理をカスタム認可ページのコードに組み込むことができます。

    デモを行うため、ここではサーバー構成にもう 1 つのスコープを追加する必要があります。次の画像を参照してください。

    また、%OAuth2.Server.Authenticate.Bank という名前のカスタム Authenticate クラスへの参照も追加しました。

    では、銀行の認証クラスはどのようになるのでしょうか? 次は想定されるクラスの例です。 このクラスは、ユーザーが提供するデータを使用して標準の認証フォームと認可フォームを拡張します。 BeforeAuthenticateDisplayPermissionsAfterAuthenticate の各メソッド間を流れる情報は、%OAuth2.Server.Properties クラスの properties 変数によって渡されます。

    Class %OAuth2.Server.Authenticate.Bank Extends %OAuth2.Server.Authenticate
    {
    /// account(口座)のスコープに CUSTOM BESTBANK のサポートを追加します。
    ClassMethod BeforeAuthenticate(scope As %ArrayOfDataTypes, properties As %OAuth2.Server.Properties) As %Status
    {
     // 起動スコープが指定されていない場合は何もしません。
     If 'scope.IsDefined("account") Quit $$$OK
     // 起動クエリパラメーターから起動コンテキストを取得します。
     Set tContext=properties.RequestProperties.GetAt("accno")
     // コンテキストがない場合は何もしません。
     If tContext="" Quit $$$OK
        
     try {
        // ここで BestBank コンテキストを照会する必要があります。
        Set tBankAccountNumber=tContext
        // accno のスコープを追加します。 -> 動的にスコープを変更(account なし:<accno> スコープはサーバー構成に存在します)
        // この特定のスコープは、それが Cookie サポートを使用して account または account:accno によって
        // 以前に選択されていた場合に account 経由で同じ accno にアクセスできるようにするために使用されます。
        Do scope.SetAt("Access data for account "_tBankAccountNumber,"account:"_tBankAccountNumber)
        // 処理が終わった account のスコープはもう必要ありません。
        // これにより、account スコープが存在することで DisplayPermissions が強制的に呼び出されるのを防ぎます。
        Do scope.RemoveAt("account")
        
        // AfterAuthenticate が応答プロパティに変換する accno プロパティを追加します。
        Do properties.CustomProperties.SetAt(tBankAccountNumber,"account_number")
     } catch (e) {
        s ^dk("err",$i(^dk("err")))=e.DisplayString()
     }
     Quit $$$OK
    }
    
    /// account のスコープに CUSTOM BESTBANK のサポートを追加します。
    /// account_number カスタムプロパティが BeforeAuthenticate(account)または
    /// DisplayPermissions(account:accno)によって追加された場合は、必要な応答プロパティを追加します。
    ClassMethod AfterAuthenticate(scope As %ArrayOfDataTypes, properties As %OAuth2.Server.Properties) As %Status
    {
     // account_number(account)または accno(account:accno)プロパティが存在しない限り、ここで実行することは何もありません。
     try {
        // カスタムログを記録する例
        If $$$SysLogLevel>=3 {
         Do ##class(%OAuth2.Utils).LogServerScope("log ScopeArray-CUSTOM BESTBANK",%token)
        }
        If properties.CustomProperties.GetAt("account_number")'="" {
         // 応答に accno クエリパラメーターを追加します。
         Do properties.ResponseProperties.SetAt(properties.CustomProperties.GetAt("account_number"),"accno")
        }
     } catch (e) {
        s ^dk("err",$i(^dk("err")))=e.DisplayString()
     }
     Quit $$$OK
    }
    
    /// BEST BANK の account のテキストを含むように変更された DisplayPermissions
    ClassMethod DisplayPermissions(authorizationCode As %String, scopeArray As %ArrayOfDataTypes, currentScopeArray As %ArrayOfDataTypes, properties As %OAuth2.Server.Properties) As %Status
    {
     Set uilocales = properties.RequestProperties.GetAt("ui_locales")
     Set tLang = ##class(%OAuth2.Utils).SelectLanguage(uilocales,"%OAuth2Login")
     // $$$TextHTML(Text,Domain,Language)
     Set ACCEPTHEADTITLE = $$$TextHTML("OAuth2 Permissions Page","%OAuth2Login",tLang)
     Set USER = $$$TextHTML("User:","%OAuth2Login",tLang)
     Set POLICY = $$$TextHTML("Policy","%OAuth2Login",tLang)
     Set TERM = $$$TextHTML("Terms of service","%OAuth2Login",tLang)
     Set ACCEPTCAPTION = $$$TextHTML("Accept","%OAuth2Login",tLang)
     Set CANCELCAPTION = $$$TextHTML("Cancel","%OAuth2Login",tLang)
     &html<<html>>
     Do ..DrawAcceptHead(ACCEPTHEADTITLE)
     Set divClass = "permissionForm"
     Set logo = properties.ServerProperties.GetAt("logo_uri")
     Set clientName = properties.ServerProperties.GetAt("client_name")
     Set clienturi = properties.ServerProperties.GetAt("client_uri")
     Set policyuri = properties.ServerProperties.GetAt("policy_uri")
     Set tosuri = properties.ServerProperties.GetAt("tos_uri")
     Set user = properties.GetClaimValue("preferred_username")
     If user="" {
        Set user = properties.GetClaimValue("sub")
     }
     &html<<body>>
     &html<<div id="topLabel"></div>>
     &html<<div class="#(divClass)#">>
     If user '= "" {
        &html<
         <div>
         <span id="left" class="userBox">#(USER)#<br>#(##class(%CSP.Page).EscapeHTML(user))#</span>
         >
     }
     If logo '= "" {
        Set espClientName = ##class(%CSP.Page).EscapeHTML(clientName)
       &html<<span class="logoClass"><img src="#(logo)#" alt="#(espClientName)#" title="#(espClientName)#" align="middle"></span>>
     }
     If policyuri '= "" ! (tosuri '= "") {
       &html<<span id="right" class="linkBox">>
        If policyuri '= "" {
         &html<<a href="#(policyuri)#" target="_blank">#(POLICY)#</a><br>>
        }
        If tosuri '= "" {
         &html<<a href="#(tosuri)#" target="_blank">#(TERM)#</a>>
        }
       &html<</span>>
     }
     &html<</div>>
     &html<<form>>
     Write ##class(%CSP.Page).InsertHiddenField("","AuthorizationCode",authorizationCode),!
     &html<<div>>
     If $isobject(scopeArray), scopeArray.Count() > 0 {
        Set tTitle = $$$TextHTML(" is requesting these permissions:","%OAuth2Login",tLang)
       &html<<div class="permissionTitleRequest">>
        If clienturi '= "" {
         &html<<a href="#(clienturi)#" target="_blank">#(##class(%CSP.Page).EscapeHTML(clientName))#</a>>
        } Else {
         &html<#(##class(%CSP.Page).EscapeHTML(clientName))#>
        }
       &html<#(##class(%CSP.Page).EscapeHTML(tTitle))#</div>>
        Set tCount = 0
        Set scope = ""
        For {
         Set display = scopeArray.GetNext(.scope)
         If scope = "" Quit
         Set tCount = tCount + 1
         If display = "" Set display = scope
         Write "<div class='permissionItemRequest'>"_tCount_". "_##class(%CSP.Page).EscapeHTML(display)_"</div>"
        }
     }
    
     If $isobject(currentScopeArray), currentScopeArray.Count() > 0 {
        Set tTitle = $$$TextHTML(" already has these permissions:","%OAuth2Login",tLang)
       &html<<div>>
       &html<<div class="permissionTitleExisting">>
        If clienturi '= "" {
         &html<<a href="#(clienturi)#" target="_blank">#(##class(%CSP.Page).EscapeHTML(clientName))#</a>>
        } Else {
         &html<#(##class(%CSP.Page).EscapeHTML(clientName))#>
        }
       &html<#(##class(%CSP.Page).EscapeHTML(tTitle))#</div>>
        Set tCount = 0
        Set scope = ""
        For {
         Set display = currentScopeArray.GetNext(.scope)
         If scope = "" Quit
         Set tCount = tCount + 1
         If display = "" Set display = scope
         Write "<div class='permissionItemExisting'>"_tCount_". "_##class(%CSP.Page).EscapeHTML(display)_"</div>"
        }
       &html<</div>>
     }
    
     /*********************************/
     /*  BEST BANK CUSTOMIZATION      */
     /*********************************/
     try {
        If properties.CustomProperties.GetAt("account_number")'="" {
         // Display the account number obtained from account context.
         Write "<div class='permissionItemRequest'><b>Selected account is "_properties.CustomProperties.GetAt("account_number")_"</b></div>",!
    
         // or, alternatively, let user add some more information at this stage (e.g. linked account number)
         //Write "<div>Account Number: <input type='text' id='accno' name='p_accno' placeholder='accno' autocomplete='off' ></div>",!
        }
     } catch (e) {
        s ^dk("err",$i(^dk("err")))=e.DisplayString()
     }
    
     /* original implementation code continues here... */
     &html<
       <div><input type="submit" id="btnAccept" name="Accept" value="#(ACCEPTCAPTION)#"/></div>
       <div><input type="submit" id="btnCancel" name="Cancel" value="#(CANCELCAPTION)#"/></div>
        >
     &html<</form>
     </div>>
     Do ..DrawFooter()
     &html<</body>>
     &html<<html>>
     Quit 1
    }
    
    /// CUSTOM BESTBANK の場合、入力された患者を検証する必要があります。
    /// ! このメソッドの javascript はユーザーに追加データを DisplayPermissions メソッド内
    /// で入力させる場合にのみ必要です !
    ClassMethod DrawAcceptHead(ACCEPTHEADTITLE)
    {
     &html<<head><title>#(ACCEPTHEADTITLE)#</title>>
     Do ..DrawStyle()
     &html<
     <script type="text/javascript">
     function doAccept()
     {
        var accno = document.getElementById("accno").value;
        var errors = "";
        if (accno !== null) {
         if (accno.length < 1) {
           errors = "Please enter account number name";
         }
        }
        if (errors) {
         alert(errors);
         return false;
        }
        
        // submit the form
        return true;
     }
     </script>
     >
     &html<</head>>
    }
    
    }
    
    

    ご覧のとおり、%OAuth2.Server.Properties クラスにはいくつかの配列が含まれており、渡されています。 具体的には、以下の配列です。

    ·        RequestProperties – 認可リクエストのパラメーターが含まれています。

    ·        CustomProperties – 上記のコードの間でデータをやり取りするためのコンテナ。

    ·        ResponseProperties – トークンリクエストに対する JSON 応答オブジェクトに追加されるプロパティのコンテナ。

    ·        ServerProperties – 認可サーバーがカスタマイズコードに公開する共有プロパティが含まれます(logo_uri、client_uri など…)

    さらに、認可サーバーが返す必要のあるクレームを指定するのに使用されるいくつかの "claims" プロパティが含まれています。

    この認証ページを正しく呼び出すため、最初のクライアントページのコードを次のように変更しました。

     set scope="openid profile scope1 scope2 account"
     // このデータはアプリケーションに由来し(フォームデータなど)、リクエストのコンテキストを設定します。
     // ここでは Authenticate クラスをサブクラス化することで、このデータをユーザーに表示することができます。
     // それにより、該当ユーザーはアクセスを許可するかどうかを決めることができます。
     set properties("accno")="75-452152122-5320"
     set url=##class(%SYS.OAuth2.Authorization).GetAuthorizationCodeEndpoint(
       ..#OAUTH2APPNAME,
        scope,
       ..#OAUTH2CLIENTREDIRECTURI,
        .properties,
       .isAuthorized,
        .sc)
     if $$$ISERR(sc) {
        write "GetAuthorizationCodeEndpoint Error="
       write ..EscapeHTML($system.Status.GetErrorText(sc))_"<br>",!
     }

    ご覧のとおり、ここではアプリケーションのさまざまな部分から発生する可能性のあるコンテキスト値を使用して account のスコープとプロパティ配列ノード “accno” を追加しました。 この値はアクセストークンの内部でリソースサーバーに渡され、さらに処理されます。

    上記のロジックは、電子的な患者記録を交換するための FHIR 標準で実際に使用されています。

    デバッグ

    OAUTH フレームワークには、デバッグ機能が組み込まれています。 クライアントとサーバー間の通信はすべて暗号化されているため、これは非常に便利です。 デバッグ機能を使用すると、API クラスによって生成されたトラフィックデータをネットワーク経由で送信する前にキャプチャできます。 コードをデバッグするため、以下のコードに従って単純なルーチンまたはクラスを実装できます。 InterSystems IRIS インスタンスのすべての通信でこのコードを実装する必要があることに注意してください! その場合、OAUTH フローのプロセス内での役割を示す名前をファイル名に指定することをお勧めします。 (以下のサンプルコードは rr.mac ルーチンとして保存されていますが、どんな名前を付けるかはあなた次第です。)

     // d start^rr()
    start() public {
     new $namespace
     set $namespace="%sys"
     kill ^%ISCLOG
     set ^%ISCLOG=5
     set ^%ISCLOG("Category","OAuth2")=5
     set ^%ISCLOG("Category","OAuth2Server")=5
     quit
    }
    
     // d stop^rr()
    stop() public {
     new $namespace
     set $namespace="%sys"
     set ^%ISCLOG=0
     set ^%ISCLOG("Category","OAuth2")=0
     set ^%ISCLOG("Category","OAuth2Server")=0
     quit
    
    }
    
     // display^rr()
    display() public {
     new $namespace
     set $namespace="%sys"
     do ##class(%OAuth2.Utils).DisplayLog("c:\temp\oauth2_auth_server.log")
     quit
    }

    次に、テストを開始する前にターミナルを開き、すべての InterSystems IRIS ノード(クライアント、認可サーバー、リソースサーバー)で d start^rr() を呼び出してください。 完了後、d stop^rr() d display^rr() を実行してログファイルを読み込んでください。

    最後に

    この連載記事では、InterSystems IRIS OAuth 2.0 の実装を使用する方法を学びました。 パート1では簡単なクライアントアプリケーションのデモを行い、パート2では複雑な例を説明しました。 最後に、OAuth 2.0 の実装で最も重要なクラスについて説明し、ユーザーアプリケーション内でそれらを呼び出す必要がある場合について説明しました。

    時々私が投げかけるくだらない質問に我慢強く回答し、この連載をレビューしてくれた Marvin Tener に心から感謝の意を表します。

     

    0
    0 374
    記事 Shintaro Kaminaka · 8月 20, 2020 23m read

    作成者:Daniel Kutac(InterSystems セールスエンジニア) 注意: 使用されている URL に戸惑っている方のために。*元の連載記事では、dk-gs2016 と呼ばれるマシンの画面を使用していました。 新しいスクリーンショットは別のマシンから取得されています。 *WIN-U9J96QBJSAG という URL は dk-gs2016 であると見なしても構いません。

    パート2. 認可サーバー、OpenID Connect サーバー

    この短い連載の前のパートでは、OAUTH[1] クライアントとして機能する単純な使用事例について学びました。 今回は私たちの経験をまったく新しいレベルに引き上げましょう。 InterSystems IRIS がすべての OAUTH の役割を果たす、より複雑な環境を構築します。 クライアントの作成方法はすでに分かっていますので、認可サーバーだけでなく、OpenID Connect[2] プロバイダーにも注意を向けましょう。 前のパートと同様に、環境を準備する必要があります。 今回はより多くの変動要素があるため、より注意を要します。

    具体例を見る前に、OpenID Connect について少し説明する必要があります。 前のパートの内容を覚えていらっしゃるかと思いますが、Google から認可してもらうため、まずは自身がGoogle で認証を受けることを求められていました。 認証は OAUTH フレームワークには含まれていません。 実際、OAUTH に依存しない多くの認証フレームワークがあります。 そのうちの1つに OpenID と呼ばれるものがあります。 当初は単独の構想で開始されましたが、最近では OAUTH フレームワークによって提供されるインフラストラクチャ、つまり通信とデータ構造が活用されています。 その結果、OpenID Connect が誕生しました。 事実、多くの人がこれを OAUTH の強化版と呼んでいます。 実際、OpenID Connect を使用すると、認可するだけでなく、OAUTH フレームワークのよく知られたインターフェースを使用して認証することもできます。

    複雑な OpenID Connect のデモ

    ここでは、パート 1 のクライアントコードの多くを活用します。 そうすることで多くの手間が省けるため、環境のセットアップに集中できます。

    前提条件

    今回は、SSL が有効になっている既存の Web サーバーに PKI インフラストラクチャを追加する必要があります。 OpenID Connect の要件である暗号化が必要です。 ユーザー認証が必要な場合は、第三者がエージェント(クライアント、認証サーバーなど)になりすますし、ネットワーク経由でユーザーの機密データを送信できないようにする必要があります。 そこで X.509 ベースの暗号化の出番です。

    注意 : Cache 2017.1 以降では、X.509 証明書を使用して JWT / JWKS(JSON Web Key Set)を生成する必要はありません。 ここでは下位互換性と単純化を図るためにこのオプションを使用しています。
    ### PKI 厳密に言えば、Caché PKI インフラストラクチャを使用する必要はまったくありません。ただし、openssl などのツールを直接使用してすべての証明書を生成するよりは楽です。 詳細は InterSystems IRIS の[ドキュメント](http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GCAS_pki#GCAS_C157792)や他の場所でも確認できますので、ここでは証明書の生成に関する詳細は触れません。 証明書を生成した結果、3 つの公開鍵/秘密鍵のペアと関連する証明書が作成されます。 これらを次のように呼びます。
    • 発行元認証局の root_ca(root_ca.cer)

    • 認可サーバーおよび OpenID サーバー用の auth(auth.cer および auth.key)

    • クライアントアプリケーションサーバー用の client(client.cer および client.key)

    X.509 資格情報

    デモ中に交換された JSON Web Token(JWT)に署名して検証できるよう、個々のサーバーで X.509 資格情報を定義する必要があります。

    認可サーバーと認証サーバーの構成

    ここでは X.509 資格情報の定義方法については詳しく説明せず、AUTHSERVER インスタンスの資格情報のスクリーンショットを示します。 画像のように、AUTHSERVER にはその秘密鍵と証明書がありますが、CLIENT に関しては公開鍵を含む証明書しかありません。

    クライアントサーバーの構成

    同様に、CLIENT インスタンスで資格情報が定義されています。 ここで、CLIENT には秘密鍵と証明書がありますが、AUTHSERVER に関しては公開鍵を含む証明書しかありません。

    リソースサーバーの構成

    このセットアップの例では、RESSERVER インスタンスで X509 資格情報を定義する必要はありません。

    OAUTH の構成

    この連載のパート 1 で説明した構成と同様に、サーバーを OAUTH 用に構成する必要があります。 まずは、OAUTH 構成全体の中心的なコンポーネントである AUTHSERVER インスタンスから始めましょう。

    AUTHSERVER

    System Management Portal で、System Administration > Security > OAuth 2.0 > Server Configuration を開きます。 メニューのリンクをクリックし、次のフォーム項目に入力します。

    • Host name(ホスト名)

    • Port(ポート、省略可)

    • Prefix(プレフィックス、省略可) – これら 3 つのフィールドは Issuer endpoint(発行者エンドポイント)を構成します。

    • 更新トークンを返す条件を指定します。

    • Supported grant types(サポートされているグラント種別)をチェックします。このデモでは 4 つの種別すべてにチェックします。 ただし、認可コードのみが使用されます。

    • 必要に応じて Audience required(オーディエンスを要求)をチェックします。これにより、aud プロパティが認可コードと暗黙的な許可(Implicit)のリクエストに追加されます。

    • 必要に応じて Support user session(ユーザーセッションをサポート)をチェックします。これにより、認可サーバーが現在このブラウザを使用しているユーザーのログイン状態を維持するために httpOnly Cookie が使用されます。 2 回目以降のアクセストークンの要求では、ユーザー名とパスワードの入力は求められません。

    • Endpoint intervals(エンドポイントの間隔)を指定します。

    • このサーバーでサポートされるスコープ(Supportted scopes)を定義します。

    • Customization Options(カスタマイズオプション)のデフォルト値を受け入れるか、カスタム値を入力します。**注意: **JWT が単なる opaque トークンではなくアクセストークンとして使用されるよう、Generate token class(生成トークンクラス)の値を %OAuth2.Server.Generate から %OAuth2.Server.JWT に変更してください。

    • OAuth 2.0 の要求に従い、HTTP 上の SSL を確立するための登録済み SSL 構成の名前を入力します。

    • JSON Web Token(JWT)の設定を入力します。

    以下はサンプル構成のスクリーンショットです。 サーバー構成を定義したら、サーバークライアント構成を入力する必要があります。 サーバー構成フォームのページ内で、「Client Configurations」(クライアント構成)ボタンをクリックし、CLIENT インスタンスおよび RESSERVER インスタンスの「Create New Configuration」(新しい構成の作成)をクリックします。 (IRISを使用して構成している場合は、「クライアントディスクリプション」の構成ページから以下の設定を行います。) 以下の画像は CLIENT の構成を示しています。 JWT Token(JWT トークン)タブはデフォルト値である空のままにします。 ご覧のとおり、実際のアプリケーションの場合とは異なり、フィールドには無意味なデータが入力されています。 同様に、RESSERVER の構成を示します。 ご覧のとおり、リソースサーバーに必要なのは非常に基本的な情報だけです。具体的には、Client type(クライアント種別)をリソースサーバーに設定する必要があります。 CLIENT では、より詳細な情報とクライアント種別を入力する必要があります(クライアントはクライアントシークレットをサーバーで維持し、クライアントエージェントには送信しない Web アプリケーションとして動作するため、機密(confidential)を選択します)。

    CLIENT

    SMP で、System Administration > Security > OAuth 2.0 > Client Configurations を開きます。 「Create Server Configuration」(サーバー構成の作成)ボタンをクリックし、フォームに入力して保存します。 Issuer Endpoint(発行者エンドポイント)が、AUTHSERVER インスタンスで前に定義した値に対応していることを必ず確認してください! また、認可サーバーのエンドポイントを Web サーバーの構成に従って変更する必要があります。 この場合は各入力フィールドに「authserver」を埋め込んだだけです。 次に、新しく作成された発行者エンドポイントの横にある「Client Configurations」(クライアント構成)リンクをクリックし、「Create Client Configuration」(クライアント構成の作成)ボタンをクリックします。 以上です! ここまでの手順で CLIENT と AUTHSERVER の両方を構成しました。 多くの使用事例ではこれだけで十分です。リソースサーバーは単なる AUTHSERVER のネームスペースにすぎず、結果的にすでに保護されている場合があるからです。 しかし、外部の医師が内部の臨床システムからデータを取得しようとしている使用事例に対応する場合を考えてみましょう。 このような医師がデータを取得できるようにするため、この医師のアカウント情報を監査目的と法医学的な理由でリソースサーバー内に確実に保存したいと思います。 この場合は、続けて RESSERVER を定義する必要があります。

    RESSERVER

    SMP で、System Administration > Security > OAuth 2.0 > Client Configurations を開きます。 「Create Server Configuration」(サーバー構成の作成)ボタンをクリックし、フォームに入力して保存します。 ここでは、Cache 2017.1 に実装された新機能である検出機能を使用しました。 ご覧のように、この構成は CLIENT インスタンスの対応する構成と同じデータを使用しています。 次に、新しく作成された発行者エンドポイントの横にある「Client Configurations」(クライアント構成)リンクをクリックし、「Create Client Configuration」(クライアント構成の作成)ボタンをクリックします。 X.509 資格情報から JWT を作成することはお勧めしませんが、ここでは互換性のために使用しました。 CLIENTとほとんど同じ手順でしたが、必要なものでした。 しかし、これで先に進んでコーディングできるようになりました!

    クライアントアプリケーション

    話をできる限り簡単にするため、パート1で説明した Google の例から多くのコードをリサイクルします。 クライアントアプリケーションは /csp/myclient で実行されるわずか 2 つの CSP ページからなるアプリケーションです。セキュリティは強制せず、未認証ユーザーとして実行されます。

    ページ 1

    Class Web.OAUTH2.Cache1N Extends %CSP.Page
    {
    
    Parameter OAUTH2CLIENTREDIRECTURI = "https://dk-gs2016/client/csp/myclient/Web.OAUTH2.Cache2N.cls";
    
    Parameter OAUTH2APPNAME = "demo client";
    
    ClassMethod OnPage() As %Status
    {
      &html<<html>
    
    <body>
      <h1>Cache&acute; OAuth2 プロバイダーに対する認証と認可</h1>
      <p>このページのデモでは、OAuth2 の認可を使用して Cache&acute; API 関数を呼び出す方法を示しています。
      <p>ここでは Cache&acute; の認証および認可サーバーを呼び出し、アプリケーションに別の Cache&acute; サーバーに保存されているデータへのアクセスを許可します。
     >
    
      // 適切なリダイレクトとスコープを持つ認証エンドポイントの URL を取得します。
      // 返された URL は下のボタンで使用されます。
    
      // DK: 'dankut' アカウントを使用して認証します!
      set scope="openid profile scope1 scope2"
      set url=##class(%SYS.OAuth2.Authorization).GetAuthorizationCodeEndpoint(
        ..#OAUTH2APPNAME,
        scope,
        ..#OAUTH2CLIENTREDIRECTURI,
        .properties,
        .isAuthorized,
        .sc)
      if $$$ISERR(sc) {
        write "GetAuthorizationCodeEndpoint Error="
        write ..EscapeHTML($system.Status.GetErrorText(sc))_"<br>",!
      } 
    
      &html<
      <div class="portalLogoBox"><a class="portalLogo" href="#(url)#">Authorize for <b>ISC</b></a></div>
      </body></html>>
      Quit $$$OK
    }
    
    ClassMethod OnPreHTTP() As %Boolean [ ServerOnly = 1 ]
    {
      #dim %response as %CSP.Response
      set scope="openid profile scope1 scope2"
      if ##class(%SYS.OAuth2.AccessToken).IsAuthorized(..#OAUTH2APPNAME,,scope,.accessToken,.idtoken,.responseProperties,.error) {
        set %response.ServerSideRedirect="Web.OAUTH2.Cache2N.cls"
      }
      quit 1
    }
    
    }
    

    ページ 2

    Class Web.OAUTH2.Cache2N Extends %CSP.Page
    {
    
    Parameter OAUTH2APPNAME = "demo client";
    
    Parameter OAUTH2ROOT = "https://dk-gs2016/resserver";
    
    Parameter SSLCONFIG = "SSL4CLIENT";
    
    ClassMethod OnPage() As %Status
    {
        &html<<html>
      
    <body>>
        
        // OAuth2 サーバーからのアクセストークンがあるかどうかをチェックします。
        set isAuthorized=##class(%SYS.OAuth2.AccessToken).IsAuthorized(..#OAUTH2APPNAME,,"scope1 scope2",.accessToken,.idtoken,.responseProperties,.error)
        
        // アクセストークンがあるかどうかをさらにチェックします。
        // 以下は実行可能なすべてのテストであり、あらゆるケースで必要なわけではありません。
        // 各テストで返される JSON オブジェクトが単に表示されています。
        if isAuthorized {
            write "<h3>Authorized!</h3>",!
            
            
            // JWT の場合、検証してからアクセストークンから詳細を取得します。
            set valid=##class(%SYS.OAuth2.Validation).ValidateJWT(..#OAUTH2APPNAME,accessToken,"scope1 scope2",,.jsonObject,.securityParameters,.sc)
            if $$$ISOK(sc) {
                if valid {
                    write "Valid JWT"_"<br>",!    
                } else {
                    write "Invalid JWT"_"<br>",!    
                }
                write "Access token="
                do jsonObject.%ToJSON()
                write "<br>",!
            } else {
                write "JWT Error="_..EscapeHTML($system.Status.GetErrorText(sc))_"<br>",!    
            }
            write "<br>",!
    
            // イントロスペクションエンドポイントを呼び出して結果を表示します。RFC 7662 を参照してください。
            set sc=##class(%SYS.OAuth2.AccessToken).GetIntrospection(..#OAUTH2APPNAME,accessToken,.jsonObject)
            if $$$ISOK(sc) {
                write "Introspection="
                do jsonObject.%ToJSON()
                write "<br>",!
            } else {
                write "Introspection Error="_..EscapeHTML($system.Status.GetErrorText(sc))_"<br>",!    
            }
            write "<br>",!
            
            if idtoken'="" {
                // ID トークンの検証と表示。OpenID Connect Core の仕様を参照してください。
                set valid=##class(%SYS.OAuth2.Validation).ValidateIDToken(
                    ..#OAUTH2APPNAME,
                    idtoken,
                    accessToken,,,
                    .jsonObject,
                    .securityParameters,
                    .sc)
                if $$$ISOK(sc) {
                    if valid {
                        write "Valid IDToken"_"<br>",!    
                    } else {
                        write "Invalid IDToken"_"<br>",!    
                    }
                    write "IDToken="
                    do jsonObject.%ToJSON()
                    write "<br>",!
                } else {
                    write "IDToken Error="_..EscapeHTML($system.Status.GetErrorText(sc))_"<br>",!    
                }
            } else {
                write "No IDToken returned"_"<br>",!
            }
            write "<br>",!
        
            // アプリケーションロジックには不要ですが、委任認証に渡すことができるユーザーに関する情報を提供します。
        
            // Userinfo エンドポイントを呼び出して結果を表示します。OpenID Connect Core の仕様を参照してください。
            set sc=##class(%SYS.OAuth2.AccessToken).GetUserinfo(
                ..#OAUTH2APPNAME,
                accessToken,,
                .jsonObject)
            if $$$ISOK(sc) {
                write "Userinfo="
                do jsonObject.%ToJSON()
                write "<br>",!
            } else {
                write "Userinfo Error="_..EscapeHTML($system.Status.GetErrorText(sc))_"<br>",!    
            }
            write "<p>",!
    
            /***************************************************
            *                                                  *
            *   リソースサーバーを呼び出し、結果を表示します。   *
            *                                                  *
            ***************************************************/
                    
            // オプション 1 - リソースサーバー - 定義によれば認可サーバーからのデータを信頼します。
            //     そのため、リソースサーバーに渡されたアクセストークンが有効である限り
            //  要求元を問わずデータを提供します。
            
            // オプション 2 - または委任認証(OpenID Connect)を使用して 
            //  (委任認証を保護して)別の CSP アプリケーションを呼び出すこともできます。
            //  - これはまさにこのデモで実施する内容です。
            
            
            write "<h4>リソースサーバーの呼び出し(委任認証)","</h4>",!
            set httpRequest=##class(%Net.HttpRequest).%New()
            // AddAccessToken は現在のアクセストークンをリクエストに追加します。
            set sc=##class(%SYS.OAuth2.AccessToken).AddAccessToken(
                httpRequest,,
                ..#SSLCONFIG,
                ..#OAUTH2APPNAME)
            if $$$ISOK(sc) {
                set sc=httpRequest.Get(..#OAUTH2ROOT_"/csp/portfolio/oauth2test.demoResource.cls")
            }
            if $$$ISOK(sc) {
                set body=httpRequest.HttpResponse.Data
                if $isobject(body) {
                    do body.Rewind()
                    set body=body.Read()
                }
                write body,"<br>",!
            }
            if $$$ISERR(sc) {
                write "Resource Server Error="_..EscapeHTML($system.Status.GetErrorText(sc))_"<br>",!    
            }
            write "<br>",!
        
            write "<h4>Call resource server - no auth, just token validity check","</h4>",!
            set httpRequest=##class(%Net.HttpRequest).%New()
            // AddAccessTokenは現在のアクセストークンをリクエストに追加します。
            set sc=##class(%SYS.OAuth2.AccessToken).AddAccessToken(
                httpRequest,,
                ..#SSLCONFIG,
                ..#OAUTH2APPNAME)
            if $$$ISOK(sc) {
                set sc=httpRequest.Get(..#OAUTH2ROOT_"/csp/portfolio2/oauth2test.demoResource.cls")
            }
            if $$$ISOK(sc) {
                set body=httpRequest.HttpResponse.Data
                if $isobject(body) {
                    do body.Rewind()
                    set body=body.Read()
                }
                write body,"<br>",!
            }
            if $$$ISERR(sc) {
                write "Resource Server Error="_..EscapeHTML($system.Status.GetErrorText(sc))_"<br>",!    
            }
            write "<br>",!
        } else {
            write "Not Authorized!<p>",!
            write "<a href='Web.OAUTH2.Cache1N.cls'>Authorize me</a>"
        }    
        &html<</body></html>>
        Quit $$$OK
    }
    
    }
    

    次のスクリーンショットで処理を説明します。

    AUTHSERVER インスタンスの認可 / OpenID Connect 認証サーバーのログインページ

    AUTHSERVER のユーザー同意ページ

    その後、最終的に結果ページが表示されます。

    ご覧のとおり、実際にコードを読んでもパート 1 で示したクライアントのコードとほとんど違いはありません。 ページ 2 には違いがあります。 これはデバッグ情報であり、JWT の有効性をチェックしています。 返ってきた JWT を検証した後、AUTHSERVER からのユーザー識別情報に関するデータに対してイントロスペクションを実行できました。 ここではこの情報をページに出力しただけですが、それ以上のこともできます。 上記で説明した外部の医師の使用事例と同様に、必要に応じて識別情報を使用し、それを認証目的でリソースサーバーに渡すことができます。 または、この情報をパラメーターとしてリソースサーバーへの API 呼び出しに渡すこともできます。

    次の段落では、ユーザー識別情報の使用方法について詳しく説明します。

    リソースアプリケーション

    リソースサーバーは認可 / 認証サーバーと同じサーバーにすることができ、多くの場合はそうなります。 しかし、このデモでは 2 つのサーバーを独立した InterSystems IRIS インスタンスにしました。

    したがって、リソースサーバーでセキュリティコンテキストを使用する方法には 2 つあります。

    方法 1 – 認証なし

    これは最も単純なケースです。 認可 / 認証サーバーはまったく同じ Caché インスタンスです。 この場合、単一の目的のために特別に作成された csp アプリケーションにアクセストークンを渡すだけで、OAUTH を使用するクライアントアプリケーションにデータを提供し、データを要求することを認可できます。

    リソース csp アプリケーションの構成(ここでは /csp/portfolio2 と呼びました)は、以下のスクリーンショットのようになります。

    最小限のセキュリティをアプリケーション定義に組み込み、特定の CSP ページのみを実行できるようにします。

    または、リソースサーバーは従来の Web ページの代わりに REST API を提供できます。 実際のシナリオでは、セキュリティコンテキストを微調整するのはユーザー次第です。

    ソースコードの例:

    Class oauth2test.demoResource Extends %CSP.Page
    {
    
    ClassMethod OnPage() As %Status
    {
        set accessToken=##class(%SYS.OAuth2.AccessToken).GetAccessTokenFromRequest(.sc)
        if $$$ISOK(sc) {
            set sc=##class(%SYS.OAuth2.AccessToken).GetIntrospection("RESSERVER resource",accessToken,.jsonObject)
            if $$$ISOK(sc) {        
                // 必要に応じて jsonObject のフィールドを検証します
    
                w "<p><h3>Hello from Cach&eacute; server: <i>/csp/portfolio2</i> application!</h3>"
                w "<p>running code as <b>$username = "_$username_"</b> with following <b>$roles = "_$roles_"</b> at node <b>"_$p($zu(86),"*",2)_"</b>."
            }
        } else {
            w "<h3>NOT AUTHORIZED!</h3>"    
            w "<pre>"
            w
            i $d(%objlasterror) d $system.OBJ.DisplayError()
            w "</pre>"
        }
        Quit $$$OK
    }
    
    }
    

    方法 2 – 委任認証

    これはもう 1 つの極端なケースです。ユーザーがリソースサーバーの内部ユーザーと同等のセキュリティコンテキストで作業しているかのように、リソースサーバーでユーザーの識別情報を可能な限り最大限に活用したいと考えています。

    解決方法の 1 つは、委任認証を使用することです。

    この方法を実行するには、さらにいくつかの手順を実行してリソースサーバーを構成する必要があります。

    ·        委任認証を有効にする

    ·        ZAUTHENTICATE ルーチンを提供する

    ·        Web アプリケーションを構成する(この場合、/csp/portfolio で呼び出しました)

    ここではユーザー識別情報とそのスコープ(セキュリティプロファイル)を提供した AUTHSERVER を信頼しているため、ZAUTHENTICATE ルーチンの実装は非常に単純で簡単です。そのため、ここではいかなるユーザー名も受け入れ、それをスコープと共にリソースサーバーのユーザーデータベースに渡しています(OAUTH スコープと InterSystems IRIS のロール間で必要な変換を行ったうえで)。 それだけです。 残りの処理は InterSystems IRIS によってシームレスに行われます。

    これは ZAUTHENTICATE ルーチンの例です。

    #include %occErrors
    #include %occInclude
    
    ZAUTHENTICATE(ServiceName, Namespace, Username, Password, Credentials, Properties) PUBLIC
    {
        set tRes=$SYSTEM.Status.OK()
        try {        
            set Properties("FullName")="OAuth account "_Username
            //set Properties("Roles")=Credentials("scope")
            set Properties("Username")=Username
            //set Properties("Password")=Password
            // Credentials 配列を GetCredentials() メソッドから渡せないため、一時的に書き換えます。
            set Properties("Password")="xxx"    // OAuth2 アカウントのパスワードは気にしません。
            set Properties("Roles")=Password
        } catch (ex) {
            set tRes=$SYSTEM.Status.Error($$$AccessDenied)
        }
        quit tRes
    }
    
    GetCredentials(ServiceName,Namespace,Username,Password,Credentials) Public 
    {
        s ts=$zts
        set tRes=$SYSTEM.Status.Error($$$AccessDenied)        
    
         try {
             If ServiceName="%Service_CSP" {
                set accessToken=##class(%SYS.OAuth2.AccessToken).GetAccessTokenFromRequest(.sc)
                if $$$ISOK(sc) {
                    set sc=##class(%SYS.OAuth2.AccessToken).GetIntrospection("RESSERVER resource",accessToken,.jsonObject)
                    if $$$ISOK(sc) {
                        // ToDo: 標準アカウントと委任されたアカウント(OpenID)が競合する可能性があるため、注意してください!
                        set Username=jsonObject.username
                        set Credentials("scope")=$p(jsonObject.scope,"openid profile ",2)
                        set Credentials("namespace")=Namespace
                        // temporary hack
                        //set Password="xxx"
                        set Password=$tr(Credentials("scope")," ",",")
                        set tRes=$SYSTEM.Status.OK()
                    } else {
                        set tRes=$SYSTEM.Status.Error($$$GetCredentialsFailed) 
                    }
                }    
            } else {
                set tRes=$SYSTEM.Status.Error($$$AccessDenied)        
            }
         } catch (ex) {
             set tRes=$SYSTEM.Status.Error($$$GetCredentialsFailed)
        }
        Quit tRes
    }
    

    CSP ページ自体は非常にシンプルになります。

    Class oauth2test.demoResource Extends %CSP.Page
    {
    
    ClassMethod OnPage() As %Status
    {
        // アクセストークン認証は委任認証によって実行されます!
        // もう一度ここで行う必要はありません。
    
        // これはリクエストからアクセストークンを取得し、イントロスペクションエンドポイントを
        // 使用してアクセストークンの有効性を確認するダミーのリソースサーバーです。
        // 通常、応答はセキュリティに関連しませんが、リクエストパラメーターに基づく
        // 興味深いデータが含まれている可能性があります。
        w "<p><h3>Hello from Cach&eacute; server: <i>/csp/portfolio</i> application!</h3>"
        w "<p>running code as <b>$username = "_$username_"</b> with following <b>$roles = "_$roles_"</b> at node <b>"_$p($zu(86),"*",2)_"</b>."
        Quit $$$OK
    }
    
    }
    

    そして最後に、/csp/portfolio の Web アプリケーション構成を示します。

    あなたが本当に心配であったなら、最初のバリエーションで行ったように _Permitted クラス_を設定できたかもしれません。 または、REST API を使用していたかもしれません。 しかし、これらの設定に関してはこの記事の中では説明しません。

    次回は、InterSystems IRIS OAUTH フレームワークによって導入される個々のクラスについて説明します。 それらの API、およびそれらを呼び出すタイミングと場所について説明します。

     

    [1] この記事で OAUTH について言及する場合、常に RFC 6749(https://tools.ietf.org/html/rfc6749)で規定されている OAuth 2.0 を指しています。 ここでは簡略化のために短縮形の OAUTH を使用しています。

    [2] OpenID Connect は OpenID Foundation(http://openid.net/connect)によって管理されています。

    0
    0 446