開発者:Java
 
 

Javaアプリケーションにおける行レベル・セキュリティの実装


著者:Lonneke Dikmans、Oracle Fusion Middlewareリージョナル・ディレクター

データベースからキャッシュまで、JEEアプリケーション全体に渡る行レベルのセキュリティを実施する方法について学習します。

2007年7月公開

セキュリティは、アプリケーションにおいて非常に重要なパートです。 ユーザーに対して認証をおこない、データについては不正なアクセスから保護する必要があります。 オラクルでは、データベースにおける行レベルのセキュリティを簡単に実現できるソリューションとして、Oracle Label Securityを提供しています。 また、オープンソースの Oracle TopLinkをORM(オブジェクト・リレーショナル・マッピング)ソリューションとして使用することで、キャッシュに加えてアプリケーションのほかの部分でも、この行レベルのセキュリティが維持されるようにすることもできます。 ここでは、簡単なサンプル・アプリケーションを使用して、行レベルのセキュリティを実装する方法について説明します。

サンプル・アプリケーション

ここで使用する サンプル・アプリケーションは、HRスキーマを使用するシンプルなWebアプリケーションです。 ユーザーは、このアプリケーションにログインし、閲覧権限を持つロケーションすべてを参照することができます。

以下のアプリケーション概要図(図1)には、次の4つが示されています。

  • データソース・レイヤー: Oracle RDBMSに含まれているHRスキーマです。 このアプリケーションでは、国、ロケーション、LOCATION_SEQのオブジェクトだけを使用します。 LOCATIONS表用に定義されるポリシーについては、次のセクションで説明します。
  • ドメイン・レイヤー: このレイヤーでは、Java、Oracle TopLink、EJB(Enterprise JavaBeans)セッション・ファサードを使用して、特定のドメイン・クラスに関する簡単な操作を実行します。 この機能は、ローカルとリモートの両方のインタフェースで使用できます。
  • プレゼンテーション・レイヤー: 単一のJavaServer Faces(JSF)ページで構成されており、ここに各ユーザーが参照を許可されているロケーションがすべて表示されています。 ここでは、サンプルを簡単なものとするためにHTTP基本認証を使用し、ドメイン・レイヤーへの接続にはADFデータ・バインディングを使用しています。
  • 統合テスト: このコンポーネントには、ドメイン・レイヤーとデータソース・レイヤーとの統合テストが含まれています。 ドメイン・レイヤーに対するリモート・インタフェースが使用されます。

図1
図1 サンプル・アプリケーションの概要図

Oracle Label Securityの概要

オラクルでは、データベースにおける多様なアクセス制御を提供しています。 ここでは、各アクセス制御の詳細について説明していきます。

任意アクセス制御(DAC): DACでは、データベース・ユーザーに対してオブジェクトレベルの権限が付与されます。 この場合、アクセスの許可または拒否の対象は、オブジェクト全体となります。 たとえば、データベース・ユーザーSKINGに対しては、以下のステートメントで、LOCATIONSから選択する権限を付与できます。

grant select to hr.locations to sking;
これで、SKINGはLOCATIONS表のすべての行を選択できます。 また、データベース・ユーザーを使用するかわりに、ロールを定義することもできます。 この場合は、データベース・ユーザーに対して選択権限を付与するのではなく、ロールに対して権限を付与してから、そのロールをデータベース・ユーザーに割り当てます。
create role emp_role;
grant connect to emp_role;
grant select to hr.locations to emp_role;
grant emp_role to SKING;
ファイングレイン・アクセス・コントロール(FGAC) FGACは、データの内容に基づいてアクセスを制限できるメソッドで、行レベルのセキュリティとも呼ばれます。 このタイプの要件に対するオラクルのデータベース・ソリューションとしては、Enterprise Editionの機能の1つである Virtual Private Database(VPD)が挙げられます。 このデータベースでは、セキュリティ・ポリシーとセッション・コンテキストに基づいて問合せが動的に変更されます。 また、返された行に対する制限のほかに、列のマスキングも可能です。

セキュリティ・ポリシーは、ストアド・プロシージャを使用して作成できます。 これらのポリシーでは、Oracle Databaseに保存されたアプリケーション・データの内容やコンテキスト変数(ユーザー名、IPアドレスなど)を使用してアクセスを制限します。

Oracle Label Security(OLS)はEnterprise Editionのオプションの1つで、VPDの実装です。 OLSを使用すると、管理者はPL/SQLを記述しなくても、ポリシーを作成できます。 データへのアクセスは、以下の4つの要素に基づいて仲介されます。

  • 行のラベル
  • ユーザー・セッションのラベル
  • セッションのポリシー権限
  • 表のポリシー実施オプション

行のラベル

表の各行には、それぞれの機密保護レベルに基づいてラベルを付けることができます。 各ラベルには、以下の3つのコンポーネントが含まれます。

  1. 単一レベル(機密性)のランキング
  2. ゼロまたはそれ以上の水平型コンパートメントまたはカテゴリ
  3. ゼロまたはそれ以上の階層グループ

サンプル・アプリケーションでは、"access_locations"という名称のポリシーを使用し、どのユーザーがどのロケーションを参照できるのかを決定します。 ここではコンパートメントまたはグループは使用しません。 レベルは、'public'('pub')、'confidential'('conf')、'sensitive'('sens')です。

ユーザー・ラベル

ラベル付きの行に対して、各ユーザーが保有するアクセスの種類(読取りまたは書込み)を決定するラベル認可を、ユーザーに付与できます。 ラベルが行に適用されている場合、その行を参照または変更できるのは、適用されたラベルに対するアクセス権限のあるユーザーだけです。

ユーザーがデータベースに接続する場合、ユーザー・セッションのレベルは行のレベルと常に一致します。

権限

OLSでは、認可ユーザーがポリシーの特定の部分を省略できるようにする特別な権限をサポートしています。 サンプル・アプリケーションでは、HRがスキーマの所有者であり、FULL権限が付与されています。 この権限により、ポリシーによって保護されているデータすべてに対する、読取りと書込みの完全なアクセスが許可されます。

ポリシーの実施

データを書き込む場合、ユーザーはその行のラベルを設定できます。 このラベルのレベルには、管理者によって指定された範囲内で任意のレベルを設定できます。 行ラベルを指定せずにデータを書き込んだ場合は、ユーザーのセッション・ラベルを使用して自動的に行ラベルが割り当てられます。

サンプル・アプリケーションでは、以下のユーザー・ラベルが定義されています。

ユーザー

最大読取り

最大書込み

最小書込み

デフォルト・ラベル

デフォルト行

SKING 'sens' 'sens' 'conf' 'sens' 'sens'
KPARTNER 'conf' 'conf' 'pub' 'conf' 'conf'
LDORAN 'pub' 'pub' 'pub' 'pub' 'pub'

DACとFGACの併用: 図2は、LOCATIONS表の問合せをおこなう場合の流れを示しています。

図2
図2. DACとFGACによるフロー

まず、データベースにおいて、ユーザーがDACに基づく十分な権限を保有しているかどうかを判断します。 HR.LOCATIONSからのデータ選択が許可されている場合、そのユーザーがポリシーを省略する権限を持っているかどうかの確認がデータベースによって実行されます。 ユーザーに特別な権限がない場合、アクセスはポリシーに基づいて仲介されます。 ユーザーが特別な権限を保有している場合、その権限に基づいて仲介が省略されたうえで問合せが実行されます。

サンプル・アプリケーションには、複数のユーザーがログインして同一の問合せをおこなう場合のフローを示すテスト・スクリプト(SQL*Plusで実行)が含まれています。

SQL*Plusを開いて次のように入力し、複数のユーザーのロケーションすべてを選択します。

@[path-to_file]\ols_test_security_policy.sql         
データベースに接続しているユーザーに応じて、同一の問合せに対して異なる結果が返されることを確認できます。

Oracle TopLinkセッション

Oracle TopLinkセッションは、Oracle TopLinkランタイムによる通信メカニズムです。 セッションには、さまざまなタイプがあります。 各セッションは、以下のコンポーネントで構成されています。

  • Javaオブジェクト・ビルダー: Oracle TopLinkでは、データソースからの読取り結果はオブジェクトに変換され、オブジェクトは書込み操作の際に問合せに変換されます。
  • 問合せメカニズム: セッションでは、オブジェクトに関する永続的な操作すべてが実行されます。
  • 接続プール: 接続プールとは、単一のデータソースに対する再利用可能な接続が集められたものです。 これにより、接続作成に関連するオーバヘッドが減少するため、アプリケーションのパフォーマンスは大幅に改善します。 Oracle TopLinkでは、内部接続プールのほかに、Java Platform、Enterprise Edition(JEE)サーバーまたはJDBCドライバによって提供される外部接続プールも使用できます。
  • 共有キャッシュ: キャッシュには、データベースに対して読取りまたは書込みがおこなわれたオブジェクトすべてが格納されます。 このキャッシュは、サーバー・セッションから取得したクライアント・セッションすべてにおいて共有されます。 キャッシュの共有は、パフォーマンスの点からも重要です。
以降の段落で、VPDまたはOLSが使用されていない"標準"のJava EEアプリケーションにおけるOracle TopLinkセッションについて見ていきましょう。

データの読取り: データソースからデータを読み取る場合、以下のようなOracle TopLinkランタイムとの相互作用が発生します。

  • サーバー・セッションからのクライアント・セッションの取得
  • クエリー・ビルダーを使用した問合せの作成
  • キャッシュの確認によるデータの読取り、またはキャッシュ内にオブジェクトがない場合のデータベースからのデータ読取り
  • 結果のオブジェクトへの変換
以下は、すべての国を読み取る場合のコードです。
    
/**
* finds all countries
* @return List<Country> or an empty list if none are found
*/
public List<Country> findAllCountries() {
        Session session = getSessionFactory().acquireSession(); 
        List<Country> results = 
         (List<Country>)session.executeQuery("findAllCountries", Country.class);
        session.release();
        results = (List<Country>)getSessionFactory().detach(results);

        return results;
}       
問合せはクラス・ディスクリプタで定義されます。これは、以下のようになります。
    
<class-descriptor-query-manager>
   <query-manager>
      <descriptor-alias>Countries</descriptor-alias>
         <query-list>
            <query>
               <name>findAllCountries</name>
               <query-type>
                  oracle.toplink.queryframework.ReadAllQuery
               </query-type>
               <cache-usage>Check Cache by Primary Key</cache-usage>
               <lock-mode>Do Not Acquire Locks</lock-mode>
               <distinct-state>Uncomputed Distinct</distinct-state>
               <in-memory-query-indirection-policy>
                        Throw Indirection Exception
                   ..etc..
            </query>
         </query-list>
      </query-manager>
session.executeQuery(...)は、まず共有キャッシュからの国の取得を試行します。 対象となるオブジェクトがキャッシュにない場合は、sessions.xmlで定義された接続を使用して、データベースから読み取られます。

データの永続化: データソースにデータを書き込む場合、以下のような手順が必要となります。

  1. グローバルJTA TXを使用してUnitOfWorkを取得するか、アクティブなセッションから新しい作業ユニットを取得する
  2. 取得した作業ユニットでオブジェクトを作成する
  3. クエリー・ビルダーと接続プールの接続を使用して、データベースにデータをコミットする
オブジェクトを永続化する方法の例を、以下に示します。
    
/**
* saves an entity
* @param entity that needs to persisted
* @return Object that is persisted.
*/
public Object persistEntity(Object entity) {
        UnitOfWork uow = getSessionFactory().acquireUnitOfWork();
        Object existingObject = uow.readObject(entity);
       if (existingObject != null)
                throw new RuntimeException("Entity already exists");
        Object newInstance = uow.deepMergeClone(entity);
        uow.commit();

        return newInstance;
    }    
        
UnitOfWorkはトランザクション・ユニットとして機能し、データベースへの書込みがサーバーのキャッシュにも書き込まれるようにします。 これで、読取りによって変更された国がキャッシュから取得された場合に、適切な更新が返されます。

接続プール: 通常、JEEアプリケーション開発時には、Oracle TopLinkによる外部接続プールを使用します。 これは、データの読取りと書込みの両方のために、単一の接続プールを保有することを意味します。 

デフォルトでは、TopLinkは内部接続プールを使用します。 この場合は、データの読取り用に1つと書込み用に1つの、2つのプールがあります。

図3
図3 Toplink接続プール・オプション

Oracle TopLinkとVPD

前項では、パフォーマンスのために共有キャッシュと接続プーリングを使用した、標準のJava EEアプリケーション構成について取り上げました。 ただし、サンプル・アプリケーションでは、より高度なソリューションが必要となります。 まず、全データを参照する権限がすべてのユーザーに付与されているわけではないため、ユーザー全員に対して共有キャッシュを使用することはできません。 次に、仲介を実行できるようにするために、接続されているユーザーに関する情報がデータベースに格納されている必要があります。 最後に、データベースに挿入される新しいデータのセキュリティ・レベルを制御することも必要です。

このため、Oracle TopLinkでVPDを使用する場合、独立クライアント・セッションを構成し、プロキシ認証を使用します。

独立クライアント・セッション: 独立クライアント・セッションとは、独自のセッション・キャッシュが用意されているクライアント・キャッシュです。

図4
図4 独立クライアント・セッション

VPDまたはOLSを使用する表は、それぞれ分離されている必要があります。 プロジェクト全体を分離させることも、クラスのみを分離させることもできます。 独立クラスから共有クラスへの参照は可能ですが、逆方向の参照はできません。 サンプル・アプリケーションの場合、ロケーションから国を参照しますが、国からロケーションへは参照しません。 つまり、分離はクラス・レベルで使用できることになります。 クラスを分離するには、宣言的にワークベンチを使用するかJavaコードを記述するかの2つの方法があります。

ワークベンチでは、以下の手順に従うことで、クラスを簡単に分離させることができます。
  1. ナビゲータでロケーションを選択する
  2. Cache」タブをクリックする
  3. isolated」を選択する
  4. 変更を保存する
session.xmlは、次のようになります。
    
<transactional-policy type="relational">
      <descriptor-alias>Location</descriptor-alias>
      <refresh-cache-policy/>
      <caching-policy>
         <cache-coordination>None</cache-coordination>
         <cache-isolation>Isolated</cache-isolation>
      </caching-policy>
      <query-manager type="relational"/>
      <locking-policy type="relational"/>
      <primary-key-policy>
         <primary-key-handles>
            <column-handle>
               <column-table-name>LOCATIONS</column-table-name>
               <column-name>LOCATION_ID</column-name>
            </column-handle>
         </primary-key-handles>
      </primary-key-policy>
 </transactional-policy>   
        
Oracle JDeveloperでは、マッピング・エディタを使用して独立キャッシュを構成することはできません。 セッションのカスタマイズは、2種類の方法で実行できます。1つは、セッションSessionEventListenerのpreLoginメソッドを使用する方法、もう1つはSessionManagerからgetSession()メソッドを使用する方法です。

preLoginメソッドを使用する場合、SessionEventListenerを実装し、このリスナーをセッションで登録する必要があります。

    
public class LocationEventListener extends SessionEventAdapter {

   //other methods you want to override 


 /**
 * We isolate the Location class here.
 * @param event that is raised before the session is logged in.
 */
 public void preLogin(SessionEvent event) {
   logger.info("in prelogin event");
   ClassDescriptor descriptor = event.getSession().getClassDescriptor(Location.class);
   descriptor.setIsIsolated(true);
  }
}
図5で示すように、マッピング・エディタでセッションにクラスを追加します。

図5
図5 イベント・リスナーによるセッションの構成

SessionManager.getSession(...)メソッドを使用する場合は、ログインせずにセッションを取得したときに、クラス・ディスクリプタをfalseに設定し、その後ログインできます。

    
private Server getSession(){
        Server server = (Server)sessionManager.getSession(xmlSessionConfigLoader, "hr", false);
        ClassDescriptor descriptor = server.getClassDescriptor(Location.class);
        descriptor.setIsIsolated(true);
        server.login();
        //.... rest of code....
}        

接続

Oracle Containers for J2EE(OC4J)10.1.3以降では、管理データソースとネイティブ・データソースの2種類のデータソースが用意されています。 管理データソースは、OC4Jにより提供されるjava.sql.DataSourceインタフェースの実装で、JDBCドライバまたはデータソースに対するラッパーとして機能します。 これにより、グローバル・トランザクションに参加でき、接続プールの使用が可能となります。 ネイティブ・データソースは、java.sql.DataSourceインタフェースを実装します。このデータソースは、JDBCドライバのベンダーによって提供されます。

Oracle TopLinkプロジェクトでは、さまざまなサーバー・セッションを定義できます。 以下は、管理データソース(jdbc/hrDS)およびネイティブ・データソースの例です。 これは、sessions.xmlで次のようにおこないます。

                                   
     
<?xml version = '1.0' encoding = 'UTF-8'?>
<!DOCTYPE toplink-configuration PUBLIC "-//Oracle Corp.//DTD TopLink Sessions 9.0.4//EN" "sessions_9_0_4.dtd">
<toplink-configuration>
   <session>
        <!-- managed datasource -->
      <name>hr</name>
      <project-xml>META-INF/locationMap.xml</project-xml>
      <session-type>
         <server-session/>
      </session-type>
      <login>
         <datasource>jdbc/hrDS</datasource>
         <uses-native-sequencing>true</uses-native-sequencing>
      </login>
     <!-- etc -->
   </session>
   <session>
        <!-- native datasource -->
      <name>sking</name>
      <project-xml>META-INF/locationMap.xml</project-xml>
      <session-type>
         <server-session/>
      </session-type>
      <login>
         <driver-class>oracle.jdbc.OracleDriver</driver-class>
         <connection-url>jdbc:oracle:thin:@localhost:1521:ORCL</connection-url>
         <platform-class>oracle.toplink.platform.database.oracle.Oracle10Platform</platform-class>
         <user-name>sking</user-name>
         <encryption-class-name>oracle.toplink.internal.security.JCEEncryptor</encryption-class-name>
         <encrypted-password>F21B2AE50E304BA0D81243DD794296A5</encrypted-password>
      </login>
   </session>
</toplink-configuration>

                                
SessionFactoryを使用すると、適切なセッションを名前で取得できます。 以下は、使用するセッションの定義方法です。
    
this.sessionFactory =  new SessionFactory("META-INF/sessions.xml", "hr");
      
                                  
Session session = getSessionFactory().acquireSession();    
                                
管理データソースを使用する場合、VPD固有のSQLを付加するロジックを各自で問合せに追加する必要があります。 また、より簡単な方法としてプロキシ認証の使用があります。 このサンプルでは、すべてのユーザーがデータベースで認識されているのでプロキシ認証を使用できます。 OC4J 10g(10.1.3.x)では、Oracle JDBCネイティブ・データソースによるプロキシ認証をサポートしています。 プロキシ認証は、ワークベンチまたはOracle JDeveloperで構成できません。これを構成するには、Javaコードが必要となります。

このケースでは、ユーザー名のみに基づいてプロキシ認証を使用します。 (詳細については、『 How-To Configure and Use Proxy-authentication with OC4J 10g (10.1.3)Data Sources』を参照してください。)

以下の手順を実行する必要があります。

  1. 次のように、ユーザーを変更します。 alter user ldoran grant connect through hr;
  2. 以下のコードを追加して、プロキシ認証で独立クライアント・セッションを取得します。
    /**
    * Returns an isolated client session from the server session
    * @return isolated client session.
    */
    private Session getSession() {
            Server server = 
                (Server)sessionManager.getSession(new XMLSessionConfigLoader(),  
                                                  HR_SESSION_CONFIG, 
                                                  Thread.currentThread().getContextClassLoader());
           DatabaseLogin login = (DatabaseLogin)server.getLogin().clone();
           login.dontUseExternalConnectionPooling();
            // this also sets isLazy flag to false
           ConnectionPolicy policy = new ConnectionPolicy(login);
           policy.setShouldUseExclusiveConnection(server.getDefaultConnectionPolicy(). 
                                                   shouldUseExclusiveConnection());
           // Set proxy properties into connection policy's login
           JNDIConnector connector = (JNDIConnector)login.getConnector();
           login.setConnector(new OracleJDBC10_1_0_2ProxyConnector(connector.getName())); 
            String user = getUser();
            login.setProperty(PROXYTYPE, 
                              Integer.toString(OracleConnection.PROXYTYPE_USER_NAME));
            login.setProperty(OracleConnection.PROXY_USER_NAME, user);
            return server.acquireClientSession(policy);
        }
    
    コンテキストからWebアプリケーションにログインしたユーザーを取得するには、次のように記述します。
    /**
    * Gets the Principal that logged in from the context
    * @return the name of the caller principal. 
    * 
    */
    private String getUser(){
       String user = ctx.getCallerPrincipal().getName();
       logger.info("user that logged in: " + user);
       return user;
    }
    

専用接続: VPDとOLSを使用する場合、通常アプリケーションでは、専用の接続を使用します。 専用接続は、Oracle TopLinkによって、分離データの読取りおよびセッションのライフ・サイクル期間に関する書込みをおこなうためのクライアント・セッションに割り当てられます。 この接続は、サーバー・セッションの書込み接続プールから取得されます。 また、Oracle TopLinkでは、非分離データの読取り(サンプル・アプリケーションの例では国の取得)のために、読取り接続プールから共有接続を取得します。

専用接続を使用するには、以下のコードを追加します。

//......
ConnectionPolicy policy = new ConnectionPolicy(login);
policy.setShouldUseExclusiveConnection(server.getDefaultConnectionPolicy(). 
                                        shouldUseExclusiveConnection());
//... rest of the code

    次のステップ

    ここでは、TopLinkを使用してJava EE WebアプリケーションでOracle Label Securityを実装する方法について学習しました。 また、上記のサンプルでは、jaznを使用してWebアプリケーションとデータベースでセキュリティを設定し、セキュリティ・ポリシーを定義しました。 この組合せは、Oracle Databaseのセキュリティ機能と同時にJava EEの機能も使用されるため、非常に強力です。

    実際のアプリケーションでは、Oracle Internet Directoryで両方を定義し、ユーザー、ロール、権限の管理を簡単にするという方法が有効となります。 上記の例では、データベースからのデータの読取りだけをおこない、データは永続化されていません。 データをデータベースに永続的に保持し、コミット時ロックを使用する場合は、行のない修正イベントに対してハンドラを追加する必要があります。 この例外は、セキュリティ違反がある場合およびロック例外の場合にスローされる可能性があります。 ロックの場合には、アプリケーションで再度オブジェクト永続化を試行することができます。 セキュリティ違反の場合は、例外のスローが必要となります。

    それでは、コーディングを実践してみてください。


    Lonneke DikmansはOracle ACEの一員で、Oracle Fusion Middlewareのリージョナル・ディレクターです。オランダの Approach Allianceでパートナーを管理しており、SOAおよびアジャイル開発を専門とするアーキテクトとしても活躍しています。 2000年以降は、Oracle JDeveloperを使用したJavaアプリケーションの設計、開発、および配置に従事しています。