#セキュリティ

0 フォロワー · 64 投稿

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

セキュリティに関するInterSystemsのドキュメントを参照してください。

記事 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
記事 Tomohiro Iwamoto · 8月 13, 2020 7m read

本記事について

InterSystems IRISは、管理ポータルへのアクセス方法がデフォルトではhttpとなっており、クライアントが社内、サーバがクラウドという配置の場合、なんらかの方法でトラフィックを暗号化したいと考える方も多いかと思います。
そこで、AWS上にて稼働中のIRISの管理ポータル(あるいは各種RESTサービス)との通信を暗号化する方法をいくつかご紹介したいと思います。

本記事では、アクセスにIRIS組み込みのapacheサーバを使用しています。ベンチマーク目的や本番環境のアプリケーションからのアクセス方法としては使用しないでください。
短期間・少人数での開発・動作検証・管理目的でのアクセスを暗号化する事を想定しています。

ドメイン名とメジャーな認証局発行のSSLサーバ証明書を用意できればベストなのですが、上記のような用途の場合、コスト面でなかなか難しいと思います。
ですので、下記の証明書の使用を想定しています。
- 自己署名(いわゆるオレオレ証明書)
- 自分で建てた認証局で署名した証明書(いわゆるオレオレ認証局)

また、下記のような実行環境を想定しています。

0
0 802
記事 Shintaro Kaminaka · 7月 3, 2020 17m read

この記事と後続の2つの連載記事は、InterSystems製品ベースのアプリケーションでOAuth 2.0フレームワーク(簡略化のためにOAUTHとも呼ばれます)を使用する必要のある開発者またはシステム管理者向けのユーザーガイドを対象としています。 

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

公開後の修正および変更の履歴 

  • 2016年8月3日 - 新しいバージョンのページを反映するため、Googleのクライアント設定のスクリーンショットを修正し、Google APIのスクリーンショットを更新しました。
  • 2016年8月28日 - Cache 2016.2でのJSON対応への変更を反映するため、JSON関連コードを変更しました。 
  • 2017年5月3日 - Cache 2017.1でリリースされた新しいUIと機能を反映するため、テキストと画面を更新しました。 
  • 2018年2月19日 - 最新の開発内容を反映するために、CachéをInterSystems IRISに変更しました。 製品名は変更されていますが、この記事はすべてのInterSystems製品(InterSystems IRIS Data Platform、Ensemble、Caché)を対象としています。 

パート1. クライアント 

概要 

これは、3部構成のInterSystemsによるOpen Authorization Frameworkの実装に関する連載記事の最初の記事です。 

この最初のパートでは、このトピックについて簡単に紹介し、InterSystems IRISアプリケーションが認証サーバーのクライアントとして機能し、保護されたリソースを要求する簡単なシナリオを示します。 

パート2ではより複雑なシナリオについて説明します。そこではInterSystems IRIS自体が認証サーバーとして機能するほか、OpenID Connectを介した認証サーバーとしても機能します。 

このシリーズの最後のパートでは、OAUTHフレームワーククラスの個々の部分について説明します。それらはInterSystems IRISにより実装されているからです。 

Open Authorization Framework[1]とは 

皆さんの多くはすでにOpen Authorization Frameworkとその使用目的について聞いたことがあるかと思います。 そのため、この記事では初めて同フレームワークを耳にした方のために簡単な要約を掲載します。 

現在はバージョン2.0であるOpen Authorization Framework(OAUTH)は、クライアント(データを要求するアプリケーション)とリソース所有者(要求されたデータを保持するアプリケーション)の間に間接的な信頼を確立することにより、主にWebベースのアプリケーションが安全な方法で情報を交換できるようにするプロトコルです。 この信頼自体は、クライアントとリソースサーバーの両方が認識して信頼する機関によって提供されます。 この機関は認証サーバーと呼ばれます。 

次の事例を使用して簡単に説明します。 

Jenny(OAUTH用語ではリソース所有者)がJennyCorp社のプロジェクトに取り組んでいるとします。 彼女はより大きな潜在的なビジネスのプロジェクト計画を作成し、JohnInc社のビジネスパートナーであるJohn(クライアントユーザー)にドキュメントのレビューを依頼します。 ただし、彼女はジョンに自社のVPNへのアクセスを許可することを快く思っていないので、ドキュメントをGoogleドライブ(リソースサーバー)または他の同様のクラウドストレージに置いています。 そうすることで、彼女は彼女とGoogle(認証サーバー)の間に信頼関係を確立していました。 彼女はJohnと共有するドキュメントを選びます(JohnはすでにGoogleドライブを使用しており、Jennyは彼のメールアドレスを知っています)。 

Johnはドキュメントを閲覧したいときには自分のGoogleアカウントで認証し、モバイルデバイス(タブレットやノートパソコンなど)からドキュメントエディタ(クライアントサーバー)を起動し、Jennyのプロジェクトファイルを読み込みます。 

とてもシンプルに聞こえますが、2人とGoogleの間には多くの通信が発生しています。 どの通信もOAuth 2.0仕様に準拠しているため、Johnのクライアント(リーダーアプリケーション)は最初にGoogleで認証する必要があります(OAUTHはこのステップに対応していません)。ジョンがGoogleが提供するフォームにJohnが同意して認証すると、Googleはアクセストークンを発行し、リーダーアプリケーションにドキュメントへのアクセスを許可します。 リーダーアプリケーションはアクセストークンを使用してGoogleドライブにリクエストを発行し、Jennyのファイルを取得します。 

以下の図に、個々の当事者間の通信を示しています。 

注意: どのOAUTH 2.0通信もHTTPリクエストを使用していますが、サーバーは必ずしもWebアプリケーションである必要はありません。 

InterSystems IRISを使ってこの簡単なシナリオを説明しましょう。 

簡単なGoogleドライブのデモ 

このデモでは、私たち自身のアカウントを使ってGoogleドライブに保存されているリソース(ファイルのリスト)をリクエストする小さなCSPベースのアプリケーションを作成します(ついでにカレンダーのリストも取得します)。 

前提条件 

アプリケーションのコーディングを始める前に、環境を準備する必要があります。 この環境には、SSLが有効になっているWebサーバーとGoogleのプロファイルが含まれます。 

Webサーバーの構成 

上記のように、認証サーバーとはSSLを使用して通信する必要があります。これは、OAuth 2.0がデフォルトでSSLを要求するためです。 データを安全に保つためには必要なことですよね? 

この記事ではSSLをサポートするようにWebサーバーを構成する方法は説明しませんので、お好みの各Webサーバーのユーザーマニュアルを参照してください。 皆さんの好奇心をそそるため、この詳細な例ではMicrosoft IISサーバーを使用します(後でいくつかのスクリーンショットを掲載するかもしれません)。 

Googleの構成 

Googleに登録するには、Google API Manager(https://console.developers.google.com/apis/library?project=globalsummit2016demo)を使用する必要があります 

デモのために、GlobalSummit2016Demoというアカウントを作成しました。 Drive APIが有効になっていることを確認してください。 

次に、認証情報を定義します。 

次の点に注意してください。 

承認済みのJavaScript生成元 – 呼び出し元のページに対し、ローカルで作成されたスクリプトのみを許可します。 

承認済みのリダイレクトURI – 理論上はクライアントアプリケーションを任意のサイトにリダイレクトできますが、InterSystems IRISのOAUTH実装を使用する場合は https://localhost/csp/sys/oauth2/OAuth2.Response.cls にリダイレクトする必要があります。 スクリーンショットのように複数の承認済みのリダイレクトURIを定義できますが、このデモでは2つのうち2番目のエントリのみが必要です。 

最後に、InterSystems IRISをGoogle認証サーバーのクライアントとして構成する必要があります。 

Cachéの構成 

InterSystems IRIS OAUTH2クライアントの構成は2段階で行われます。 まず、サーバー構成を作成する必要があります。 

SMPで、System Administration(システム管理) > Security(セキュリティ) > OAuth 2.0 > Client Configurations(クライアント構成)を開きます。 

「サーバー構成の作成」ボタンをクリックし、フォームに入力して保存します。 

フォームに入力したすべての情報は、Google Developers Consoleのサイトで確認できます。 InterSystems IRISはOpen IDの自動検出に対応しています。 ただし、ここでは自動検出を使用せず、すべての情報を手動で入力しました。 

次に、新しく作成された発行者エンドポイントの横にある「Client Configurations」(クライアント構成)リンクをクリックし、「Create Client Configuration」(クライアント構成を作成する)ボタンをクリックします。 

「Client Information」(クライアント情報)タブと「JWT Settings」(JWT設定)タブは空のままにし(デフォルト値を使用します)、クライアントの認証情報を入力してください。 

注意:ここでは、Confidential Clientを作成しています。これはPublic Clientよりも安全であり、クライアントシークレットがクライアントサーバーアプリケーションを離れることはありません(ブラウザに送信されません)。 

また、「Use SSL/TLS」(SSL/TLSを使用する)がチェックされ、ホスト名(ここではクライアントアプリケーションにローカルにリダイレクトしているため、localhostにします)が入力され、さらにはポートとプレフィックスが入力されていることを確認してください(これは同じマシンに複数のInterSystems IRISがある場合に役立ちます)。 入力した情報に基づいてクライアントリダイレクトURLが生成され、上の行に表示されます。 

上のスクリーンショットでは、GOOGLEという名前のSSL構成を選択しました。 この名前自体は、多くのSSL構成のうちどれをこの特定の通信チャネルで使用するかを決定するためにのみ使用されます。 CachéはSSL/TLS構成を使用し、サーバー(この場合はGoogle OAuth 2.0 URI)との安全なトラフィックを確立するために必要なすべての情報を保存しています。 

より詳細な説明については、ドキュメントを参照してください。 

Googleの認証情報定義フォームから取得したクライアントIDとクライアントシークレットの値を入力します(手動構成を使用する場合)。 

これですべての構成ステップが完了し、CSPアプリケーションのコーディングに進むことができます。 

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

クライアントアプリケーションは、シンプルなWebベースのCSPアプリケーションです。 そのため、Webサーバーによって定義および実行されるサーバー側のソースコードと、Webブラウザによってユーザーに公開されるユーザーインターフェイスで構成されています。 

クライアントサーバー 

クライアントサーバーは単純な2ページのアプリケーションです。 アプリケーション内では次の処理を実行します。 

·        Google認証サーバーのリダイレクトURLを組み立てます。 

·        Google Drive APIおよびGoogle Calendar APIへのリクエストを実行し、結果を表示します。 

ページ1 

これはアプリケーションの1ページであり、そのリソースについてGoogleを呼び出すことにしました。 

以下はこのページを表す最小限の、しかし完全に動作するコードです。 

Class Web.OAUTH2.Google1N Extends %CSP.Page 

{ 


Parameter OAUTH2CLIENTREDIRECTURI = "https://localhost/csp/google/Web.OAUTH2.Google2N.cls"; 


Parameter OAUTH2APPNAME = "Google"; 


ClassMethod OnPage() As %Status 

{ 

  &html<<html> 

<head> 

</head> 

<body style="text-align: center;"> 

        <!-- ページの内容をここに挿入します --> 

        <h1>Google OAuth2 API</h1> 

        <p>このページのデモでは、OAuth2認証を使用してGoogle API関数を呼び出す方法を示しています。 

        <p>ユーザーとユーザーのGoogleドライブのファイル、およびカレンダーエントリに関する情報を取得します。 

        > 

         

  // Googleで認証するにはopenidのスコープを指定する必要があります 

  set scope="openid https://www.googleapis.com/auth/userinfo.email "_ 

  "https://www.googleapis.com/auth/userinfo.profile "_ 

  "https://www.googleapis.com/auth/drive.metadata.readonly "_ 

  "https://www.googleapis.com/auth/calendar.readonly" 


  set properties("approval_prompt")="force" 

  set properties("include_granted_scopes")="true" 


  set url=##class(%SYS.OAuth2.Authorization).GetAuthorizationCodeEndpoint(..#OAUTH2APPNAME,scope, 

    ..#OAUTH2CLIENTREDIRECTURI,.properties,.isAuthorized,.sc)  

  w !,"<p><a href='"_url_"'><img border='0' alt='Googleサインイン' src='images/google-signin-button.png' ></a>"  


      &html<</body> 

</html>> 

  Quit $$$OK 

} 


ClassMethod OnPreHTTP() As %Boolean [ ServerOnly = 1 ] 

{ 

  #dim %response as %CSP.Response 

  set scope="openid https://www.googleapis.com/auth/userinfo.email "_ 

    "https://www.googleapis.com/auth/userinfo.profile "_ 

    "https://www.googleapis.com/auth/drive.metadata.readonly "_ 

    "https://www.googleapis.com/auth/calendar.readonly" 

  if ##class(%SYS.OAuth2.AccessToken).IsAuthorized(..#OAUTH2APPNAME,,scope,.accessToken,.idtoken,.responseProperties,.error) { 

    set %response.ServerSideRedirect="Web.OAUTH2.Google2N.cls" 

  } 

  quit 1 

  } 

} 

以下にこのコードの簡単な説明を記します。 

1.     OnPreHTTPメソッド - まず、すでに有効なアクセストークンをGoogleの認証結果として取得しているかどうかを確認します。この認証は、例えば単にページを更新したときに発生する可能性があります。 トークンを取得できていない場合は、認証する必要があります。 トークンを取得できている場合は、結果表示ページにページをリダイレクトするだけです。 

2.      OnPageメソッド - 有効なアクセストークンがない場合にのみここに到達します。その場合、認証してGoogleに対する権限を付与し、アクセストークンを付与してもらうために通信を開始しなければなりません。 

3.      Google認証ダイアログの動作を変更するスコープ文字列とプロパティ配列を定義します(私たちのIDに基づいて認証する前に、Googleに対して認証する必要があります)。 

4.      最後にGoogleのログインページのURLを受け取り、それをユーザーに提示してから同意ページを表示します。 

追加の注意事項: 

ここでは実際のリダイレクトページを https://www.localhost/csp/google/Web.OAUTH2.Google2N.cls(OAUTH2CLIENTREDIRECTURIパラメータ内)で指定しています。 ただし、Google認証情報の定義ではInterSystems IRIS OAUTH Frameworkのシステムページを使用しています。 リダイレクトは、OAUTHハンドラークラスによって内部的に処理されます。 

ページ2 

このページにはGoogle認証の結果が表示されます。成功した場合はGoogleのAPIコールを呼び出してデータを取得します。 繰り返しになりますが、このコードは最小限でも完全に機能します。 受信データがどのような構造で表示されるかは、皆さんのご想像にお任せします。 

Include %occInclude 


Class Web.OAUTH2.Google2N Extends %CSP.ページ 

{ 


Parameter OAUTH2APPNAME = "Google"; 


Parameter OAUTH2ROOT = "https://www.googleapis.com"; 


ClassMethod OnPage() As %Status 

{ 

  &html<<html> 

   <head> 

   </head> 

   <body>> 


  // アクセストークンがあるかどうかを確認します 

  set scope="openid https://www.googleapis.com/auth/userinfo.email "_ 

    "https://www.googleapis.com/auth/userinfo.profile "_ 

    "https://www.googleapis.com/auth/drive.metadata.readonly "_ 

    "https://www.googleapis.com/auth/calendar.readonly" 


  set isAuthorized=##class(%SYS.OAuth2.AccessToken).IsAuthorized(..#OAUTH2APPNAME,,scope,.accessToken,.idtoken,.responseProperties,.error) 

  if isAuthorized {  

    // Googleにはイントロスペクションエンドポイントがありませんので、呼び出す必要はありません。イントロスペクションエンドポイントと表示結果については、RFC 7662を参照してください。   

    w "<h3><span style='color:red;'>GetUserInfo API</span>からのデータ</h3>" 

    // userinfoには専用のAPIがありますが、Get() メソッドを適切なURLで呼び出すだけでも取得できます。     

    try { 

    set tHttpRequest=##class(%Net.HttpRequest).%New() 

      $$$THROWONERROR(sc,##class(%SYS.OAuth2.AccessToken).AddAccessToken(tHttpRequest,"query","GOOGLE",..#OAUTH2APPNAME)) 

      $$$THROWONERROR(sc,##class(%SYS.OAuth2.AccessToken).GetUserinfo(..#OAUTH2APPNAME,accessToken,,.jsonObject)) 

      w jsonObject.%ToJSON() 

    } catch (e) { 

      w "<h3><span style='color: red;'>エラー: ",$zcvt(e.DisplayString(),"O","HTML")_"</span></h3>"     

    } 


    /****************************************** 

    *                                         * 

    *      他のAPIから情報を取得する      * 

    *                                         * 

    ******************************************/ 

    w "<hr>" 


    do ..RetrieveAPIInfo("/drive/v3/files") 

  

    do ..RetrieveAPIInfo("/calendar/v3/users/me/calendarList") 


  } else { 

    w "<h1>認証されていません!</h1>"   

  } 

  &html<</body> 

  </html>> 

  Quit $$$OK 

} 


  


ClassMethod RetrieveAPIInfo(api As %String) 

{ 

  w "<h3><span style='color:red;'>"_api_"</span>からのデータ</h3><p>" 

  try { 

    set tHttpRequest=##class(%Net.HttpRequest).%New() 

    $$$THROWONERROR(sc,##class(%SYS.OAuth2.AccessToken).AddAccessToken(tHttpRequest,"query","GOOGLE",..#OAUTH2APPNAME)) 

    $$$THROWONERROR(sc,tHttpRequest.Get(..#OAUTH2ROOT_api)) 

    set tHttpResponse=tHttpRequest.HttpResponse 

    s tJSONString=tHttpResponse.Data.Read() 

    if $e(tJSONString)'="{" { 

      // JSONではない 

      d tHttpResponse.OutputToDevice() 

    } else {       

      w tJSONString 

      w "<hr/>" 

      /* 

      // 新しいJSON API 

      &html<<table border=1 style='border-collapse: collapse'>> 

      s tJSONObject={}.%FromJSON(tJSONString) 

      set iterator=tJSONObject.%GetIterator() 

        while iterator.%GetNext(.key,.value) { 

          if $isobject(value) { 

            set iterator1=value.%GetIterator() 

            w "<tr><td>",key,"</td><td><table border=1 style='border-collapse: collapse'>" 

            while iterator1.%GetNext(.key1,.value1) { 

            if $isobject(value1) { 

                set iterator2=value1.%GetIterator() 

                w "<tr><td>",key1,"</td><td><table border=0 style='border-collapse: collapse'>" 

                while iterator2.%GetNext(.key2,.value2) { 

                    write !, "<tr><td>",key2, "</td><td>",value2,"</td></tr>"                    

                } 

                // このようにして埋め込みオブジェクト/配列をどんどん進めていきます 

              w "</table></td></tr>" 

            } else { 

                  write !, "<tr><td>",key1, "</td><td>",value1,"</td></tr>"        

            } 

            } 

          w "</table></td></tr>" 

          } else { 

              write !, "<tr><td>",key, "</td><td>",value,"</td></tr>" 

          } 

        }        

    &html<</table><hr/> 

    > 

    */ 

    } 

  } catch (e) { 

    w "<h3><span style='color: red;'>エラー: ",$zcvt(e.DisplayString(),"O","HTML")_"</span></h3>" 

  } 

} 


} 

  

コードを簡単に見てみましょう。 

1.      まず、有効なアクセストークンがあるかどうかを確認する必要があります(そのため、認証を受けました)。 

2.      トークンがある場合はGoogleが提供し、発行されたアクセストークンがカバーするAPIにリクエストを発行できます。 

3.       そのためには標準の %Net.HttpRequest クラスを使用しますが、呼び出されたAPIの仕様に従ってGETメソッドまたはPOSTメソッドにアクセストークンを追加します。 

4.       ご覧のように、OAUTHフレームワークには GetUserInfo() メソッドが実装されていますが、RetrieveAPIInfo() ヘルパーメソッドの場合と同様に、Google APIの仕様を利用して直接ユーザー情報を取得できます。 

5.       OAUTHの世界ではJSON形式でデータを交換するのが一般的であるため、ここでは受信データを読み取り、それを単にブラウザに出力してます。 受信データを解析して整形し、それをユーザーが理解できる形で表示できるようにするのはアプリケーション開発者の責任です。 しかし、それはこのデモの範囲を超えています。 (ただし、いくつかのコメントアウトされたコードで構文解析のやり方を示しています。) 

以下は、未加工のJSONデータが表示された出力のスクリーンショットです。 

パート2に進んでください。そこでは、認証サーバーおよびOpenID Connectプロバイダーとして機能するInterSystems IRISについて説明します。 

[1]https://tools.ietf.org/html/rfc6749https://tools.ietf.org/html/rfc6750 

0
0 857
記事 Shintaro Kaminaka · 4月 28, 2020 9m read

CachéまたはEnsembleへの接続にStudio、ODBC、またはターミナルを使用している場合、その接続をどのように保護すれば良いのか疑問に思うかもしれません。 選択肢の一つに、TLS(別名SSL)を接続に追加することが挙げられます。 Cachéクライアントアプリケーション(TELNET、ODBC、Studio)にはすべて、TLSを接続に追加する機能があります。 あとは単純にその構成を行うだけです。 

2015.1以降はこれらのクライアントを簡単に設定できるようになりました。 ここでは、その新しい方法について説明します。 既に古い方法を使用している場合も引き続き機能しますが、新しい方法への切り替えを検討することをお勧めします。 

背景 

これらのクライアントアプリケーションは、サーバーがインストールされていないマシンにインストールできます。 ただし、CACHESYSデータベースやcpfファイルなど、設定を保存する通常の場所へのアクセスに依存することはできません。 その代わり、受け付ける証明書やプロトコルの設定はテキストファイルに保存されます。 このファイルの設定の多くは、管理ポータルのSSL/TLS構成の設定に似ています。 

設定ファイルはどこにありますか? 

独自のファイルを作成する必要があります。 クライアントインストーラーは設定ファイルを作成しません。 

0
0 621