マルチテナントの SaaS アプリケーションや、部門ごとにデータアクセスを分けたい場面では、アプリケーション層でのフィルタリングに頼らず、データベース層で自動的にアクセスを制限できると安全です。
VPD(Virtual Private Database)は Oracle の行レベルセキュリティ機能です。DBMS_RLS パッケージでポリシーをテーブルに適用すると、SQL 文の WHERE 句にセキュリティ条件が自動的に付加されます。アプリケーションコードを変更せずにデータアクセスを制御できます。
この記事でわかること
- VPD の仕組みと WHERE 句が自動付加される流れ
- ポリシー関数の作成方法(戻り値が WHERE 句の条件文字列)
- DBMS_RLS.ADD_POLICY でテーブルにポリシーを適用する手順
- Application Context(SYS_CONTEXT)と組み合わせたテナント分離の実装
- DML(INSERT/UPDATE/DELETE)へのポリシー適用
- DBA_POLICIES でポリシーを管理・確認する方法
VPD の仕組み
VPD のポリシーが適用されたテーブルへの SQL 実行時、Oracle は次の順番で処理します:
- ユーザーが SQL を実行する(例:
SELECT * FROM orders) - Oracle がポリシー関数を呼び出す
- ポリシー関数が WHERE 句に付加する条件文字列を返す(例:
tenant_id = 1001) - Oracle が元の SQL に条件を付加して実行する(
SELECT * FROM orders WHERE tenant_id = 1001)
| 項目 | アプリ層でのフィルタリング | VPD(行レベルセキュリティ) |
|---|---|---|
| フィルタリングの場所 | アプリケーションコード | データベースが自動付加 |
| バイパスのリスク | コードミスやSQL直接実行でバイパス可能 | データベースが強制するため回避不可 |
| SQLツール経由のアクセス | WHERE 句なしで全件取得できてしまう | ポリシーが自動適用される |
| 適用範囲 | SELECT のみ(設計次第) | SELECT / INSERT / UPDATE / DELETE を個別に制御可能 |
| パフォーマンス | インデックスが使えないケースあり | 条件が明確なためインデックスが利きやすい |
ポリシー関数を作成する
ポリシー関数は FUNCTION function_name(schema_name VARCHAR2, table_name VARCHAR2) RETURN VARCHAR2 のシグネチャを持ちます。返す文字列が WHERE 句に付加される条件です。空文字列を返すとフィルタリングなし(全件アクセス可)になります。
ログインユーザーの部門でフィルタリングするポリシー関数
-- ポリシー関数: 現在のログインユーザーの部門IDに応じてフィルタリングする
CREATE OR REPLACE FUNCTION dept_security_policy(
p_schema IN VARCHAR2,
p_table IN VARCHAR2
) RETURN VARCHAR2 AS
v_dept_id NUMBER;
v_user VARCHAR2(100);
BEGIN
v_user := SYS_CONTEXT('USERENV', 'SESSION_USER');
-- SYS や DBA はすべてのデータを見られるようにする
IF v_user IN ('SYS', 'SYSTEM', 'DBA_USER') THEN
RETURN ''; -- 空文字 = フィルタリングなし(全件)
END IF;
-- ユーザーの部門IDを取得する
BEGIN
SELECT department_id INTO v_dept_id
FROM user_dept_mapping
WHERE username = v_user;
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- マッピングがない場合は1=0(全件非表示)
RETURN '1=0';
END;
-- WHERE 句に追加する条件文字列を返す(列名はポリシー適用テーブルの列)
RETURN 'department_id = ' || v_dept_id;
END dept_security_policy;
/
DBMS_RLS.ADD_POLICY でポリシーを適用する
DBMS_RLS.ADD_POLICY の基本的な使い方
-- employees テーブルに SELECT ポリシーを適用する
BEGIN
DBMS_RLS.ADD_POLICY(
object_schema => 'HR', -- テーブルのスキーマ
object_name => 'EMPLOYEES', -- 対象テーブル(またはビュー)
policy_name => 'DEPT_SELECT_POLICY', -- ポリシー名(スキーマ内で一意)
function_schema => 'HR', -- ポリシー関数のスキーマ
policy_function => 'DEPT_SECURITY_POLICY', -- ポリシー関数名
statement_types => 'SELECT', -- 適用する SQL タイプ
enable => TRUE -- 即時有効化
);
END;
/
-- INSERT / UPDATE / DELETE にも適用する(DML ポリシー)
BEGIN
DBMS_RLS.ADD_POLICY(
object_schema => 'HR',
object_name => 'EMPLOYEES',
policy_name => 'DEPT_DML_POLICY',
function_schema => 'HR',
policy_function => 'DEPT_SECURITY_POLICY',
statement_types => 'INSERT, UPDATE, DELETE', -- 複数指定はカンマ区切り
enable => TRUE
);
END;
/
-- ポリシーを一時的に無効にする
BEGIN
DBMS_RLS.ENABLE_POLICY(
object_schema => 'HR',
object_name => 'EMPLOYEES',
policy_name => 'DEPT_SELECT_POLICY',
enable => FALSE -- FALSE で無効化
);
END;
/
-- ポリシーを削除する
BEGIN
DBMS_RLS.DROP_POLICY(
object_schema => 'HR',
object_name => 'EMPLOYEES',
policy_name => 'DEPT_SELECT_POLICY'
);
END;
/
Application Context と組み合わせたテナント分離
SaaS アプリケーションでのマルチテナント分離には、Application Context(アプリケーションコンテキスト)と VPD の組み合わせが定番パターンです。ログイン時にテナント ID をコンテキストに設定し、ポリシー関数がそれを参照します。
Application Context を設定するパッケージを作成する
-- アプリケーションコンテキストの名前空間を作成する
CREATE OR REPLACE CONTEXT tenant_ctx
USING set_tenant_context_pkg; -- コンテキストを設定できるパッケージを指定
-- コンテキストを設定するパッケージ
CREATE OR REPLACE PACKAGE set_tenant_context_pkg AS
PROCEDURE set_tenant(p_tenant_id IN NUMBER);
PROCEDURE clear_tenant;
END set_tenant_context_pkg;
/
CREATE OR REPLACE PACKAGE BODY set_tenant_context_pkg AS
PROCEDURE set_tenant(p_tenant_id IN NUMBER) AS
BEGIN
DBMS_SESSION.SET_CONTEXT(
namespace => 'TENANT_CTX',
attribute => 'TENANT_ID',
value => TO_CHAR(p_tenant_id)
);
END set_tenant;
PROCEDURE clear_tenant AS
BEGIN
DBMS_SESSION.CLEAR_CONTEXT('TENANT_CTX', NULL, 'TENANT_ID');
END clear_tenant;
END set_tenant_context_pkg;
/
テナントIDでフィルタリングするポリシー関数とポリシーの適用
-- Application Context を使ったポリシー関数
CREATE OR REPLACE FUNCTION tenant_isolation_policy(
p_schema IN VARCHAR2,
p_table IN VARCHAR2
) RETURN VARCHAR2 AS
v_tenant_id VARCHAR2(20);
BEGIN
-- Application Context からテナントIDを取得する
v_tenant_id := SYS_CONTEXT('TENANT_CTX', 'TENANT_ID');
IF v_tenant_id IS NULL THEN
-- テナントIDが設定されていない場合はアクセス拒否
RETURN '1=0';
END IF;
RETURN 'tenant_id = ' || v_tenant_id;
END tenant_isolation_policy;
/
-- orders テーブルにテナント分離ポリシーを適用する
BEGIN
DBMS_RLS.ADD_POLICY(
object_schema => 'APP',
object_name => 'ORDERS',
policy_name => 'TENANT_ISOLATION',
function_schema => 'APP',
policy_function => 'TENANT_ISOLATION_POLICY',
statement_types => 'SELECT, INSERT, UPDATE, DELETE',
enable => TRUE
);
END;
/
-- アプリケーションからの使用例
-- ① ログイン時にテナントIDをセット
EXEC set_tenant_context_pkg.set_tenant(1001);
-- ② この後の SQL にポリシーが自動適用される
SELECT * FROM app.orders; -- WHERE tenant_id = 1001 が自動付加
INSERT INTO app.orders(order_id, item) VALUES (1, 'test'); -- tenant_id=1001 の行として扱われる
-- ※ INSERT の場合、ポリシーで追加された WHERE 条件で新規行が「見える」かどうかをチェックする
-- ③ セッション終了時にクリア
EXEC set_tenant_context_pkg.clear_tenant;
DBA_POLICIES でポリシーを管理する
適用済みポリシーを確認する
-- 全ポリシーを一覧表示する
SELECT
object_owner,
object_name,
policy_group,
policy_name,
pf_owner, -- ポリシー関数のスキーマ
package, -- ポリシー関数のパッケージ名(スタンドアロン関数の場合 NULL)
function, -- ポリシー関数名
sel, -- SELECT に適用: YES/NO
ins, -- INSERT に適用: YES/NO
upd, -- UPDATE に適用: YES/NO
del, -- DELETE に適用: YES/NO
enable -- 有効: YES / 無効: NO
FROM DBA_POLICIES
WHERE object_owner = 'HR'
ORDER BY object_name, policy_name;
-- ポリシー適用後の SQL 変換結果を確認する(デバッグ用)
-- V$VPD_POLICY: 現在実行中のSQL に適用されているVPDポリシーを確認できる
SELECT object_name, policy_name, predicate
FROM V$VPD_POLICY;
-- predicate 列に、ポリシー関数が返した WHERE 句の条件文字列が表示される
まとめ
- VPD(Virtual Private Database):データベースが WHERE 句を自動付加する行レベルセキュリティ。アプリ層バイパスのリスクを排除できる
- ポリシー関数:
(schema VARCHAR2, table_name VARCHAR2) RETURN VARCHAR2のシグネチャ。返す文字列が WHERE 句の条件になる。空文字列でフィルタリングなし、’1=0′ で全件非表示 - DBMS_RLS.ADD_POLICY:statement_types で SELECT / INSERT / UPDATE / DELETE を個別に指定できる
- Application Context との組み合わせ:ログイン時に DBMS_SESSION.SET_CONTEXT でテナントIDを設定し、ポリシー関数が SYS_CONTEXT で参照するパターンがマルチテナント分離の定番
- DBA_POLICIES:適用済みポリシーを管理。V$VPD_POLICY でデバッグ時に実際の述語を確認できる
Oracle の監査機能については 監査(AUDIT)の設定方法を参照してください。SYS_CONTEXT の詳細は DBMS_APPLICATION_INFO完全ガイドも参照してください。