DBMS_SESSION.SET_CONTEXT は、Oracleのアプリケーションコンテキストへ値を設定するための手続きです。テナントID、ログインユーザーID、部門ID、ロール、リクエストIDなどをセッションに保持し、SYS_CONTEXT でSQLやPL/SQLから参照できます。
この記事では、アプリケーションコンテキスト全体の仕様解説ではなく、PL/SQLで SET_CONTEXT を安全に設定・参照・クリアする実装テンプレートに絞って整理します。体系的な仕様は SYS_CONTEXT・アプリケーションコンテキスト完全ガイド、VPD全体の考え方は VPD(Virtual Private Database)完全ガイド もあわせて確認してください。
CREATE CONTEXT ... USINGと信頼済みパッケージDBMS_SESSION.SET_CONTEXT/SYS_CONTEXTの基本- 接続プールでの設定・クリアテンプレート
CLEAR_CONTEXT、CLEAR_ALL_CONTEXT、LIST_CONTEXTSET_IDENTIFIERと監査ログへの活用- VPD /
DBMS_RLSとの連携
アプリケーションコンテキストとは
アプリケーションコンテキストは、セッションに紐づく名前付き属性の入れ物です。名前空間を作成し、その中に TENANT_ID、APP_USER_ID、ROLE_NAME のような属性を設定します。設定した値は、SQLやPL/SQLから SYS_CONTEXT(namespace, attribute) で参照できます。
APP_CTXTENANT_IDSET_CONTEXT の値はVARCHAR2として扱います。SYS_CONTEXT でSQLやPL/SQLから参照します。CREATE CONTEXTで名前空間を作る
最初に、コンテキスト名前空間を作成します。重要なのは、USING 句で指定したパッケージが、そのコンテキストを設定できる信頼済みパッケージになる点です。Oracle公式でも、SET_CONTEXT はこの指定パッケージから直接または間接的に呼ばれる必要があると説明されています。
-- DBAまたはCREATE ANY CONTEXT権限を持つユーザーで実行 CREATE OR REPLACE CONTEXT app_ctx USING app_ctx_pkg; -- 作成後、APP_CTXの値は app_ctx_pkg 経由で設定する
CREATE CONTEXT 実行時点では、指定したパッケージが存在するかまでは厳密に検証されません。ただし実運用では、先に仕様を決め、パッケージ名・所有スキーマ・権限を明確にしてから作成する方が安全です。
信頼済みパッケージでSET_CONTEXTする
DBMS_SESSION.SET_CONTEXT は、任意のPL/SQLブロックから自由に呼べるものではありません。CREATE CONTEXT app_ctx USING app_ctx_pkg と定義したなら、app_ctx_pkg のコールスタック内から設定します。この制約により、利用者が好きなテナントIDやロールを勝手に設定する事故を防ぎます。
CREATE OR REPLACE PACKAGE app_ctx_pkg AUTHID DEFINER AS
PROCEDURE set_login_context(
p_app_user_id IN NUMBER,
p_tenant_id IN NUMBER,
p_role_name IN VARCHAR2
);
PROCEDURE clear_login_context;
END app_ctx_pkg;
/
CREATE OR REPLACE PACKAGE BODY app_ctx_pkg AS
PROCEDURE set_login_context(
p_app_user_id IN NUMBER,
p_tenant_id IN NUMBER,
p_role_name IN VARCHAR2
) IS
BEGIN
IF p_app_user_id IS NULL OR p_tenant_id IS NULL THEN
RAISE_APPLICATION_ERROR(-20001, 'login context is required');
END IF;
DBMS_SESSION.SET_CONTEXT('APP_CTX', 'APP_USER_ID', TO_CHAR(p_app_user_id));
DBMS_SESSION.SET_CONTEXT('APP_CTX', 'TENANT_ID', TO_CHAR(p_tenant_id));
DBMS_SESSION.SET_CONTEXT('APP_CTX', 'ROLE_NAME', UPPER(p_role_name));
END;
PROCEDURE clear_login_context IS
BEGIN
DBMS_SESSION.CLEAR_ALL_CONTEXT('APP_CTX');
END;
END app_ctx_pkg;
/
パッケージは AUTHID DEFINER にし、必要な検証をパッケージ内に閉じ込めます。権限境界の考え方は 権限分離とセキュリティパッケージ、AUTHID設計は AUTHIDと権限管理の設計 も参考になります。
SYS_CONTEXTで値を参照する
設定した値は SYS_CONTEXT で参照します。PL/SQLの分岐、SQLのWHERE句、監査ログ、VPDポリシー関数などで使えます。
SELECT SYS_CONTEXT('APP_CTX', 'APP_USER_ID') AS app_user_id,
SYS_CONTEXT('APP_CTX', 'TENANT_ID') AS tenant_id,
SYS_CONTEXT('APP_CTX', 'ROLE_NAME') AS role_name
FROM dual;
CREATE OR REPLACE FUNCTION current_tenant_id
RETURN NUMBER
IS
BEGIN
RETURN TO_NUMBER(SYS_CONTEXT('APP_CTX', 'TENANT_ID'));
EXCEPTION
WHEN VALUE_ERROR THEN
RETURN NULL;
END;
/
SYS_CONTEXT の戻り値は文字列です。数値や日付として使う場合は、変換エラー時の扱いを決めておきます。未設定時にNULLになることも考慮し、アクセス制御では未設定を許可側に倒さない設計が重要です。
接続プールでの設定・クリア
Webアプリケーションでは、DBセッションが接続プールで使い回されます。前の利用者のコンテキストが残ると重大な情報漏えいになります。そのため、リクエスト開始時に必ず設定し、終了時または返却前に必ずクリアします。
-- リクエスト開始時
BEGIN
app_ctx_pkg.clear_login_context;
app_ctx_pkg.set_login_context(
p_app_user_id => :app_user_id,
p_tenant_id => :tenant_id,
p_role_name => :role_name
);
DBMS_SESSION.SET_IDENTIFIER(:request_id);
END;
/
-- リクエスト終了時、または接続をプールへ返す直前
BEGIN
app_ctx_pkg.clear_login_context;
DBMS_SESSION.CLEAR_IDENTIFIER;
END;
/
DBMS_SESSION.SET_IDENTIFIER は CLIENT_IDENTIFIER を設定するために使います。アプリ利用者ID、リクエストID、ジョブIDなどを入れておくと、監査ログやセッション確認がしやすくなります。監査ログ設計は ロギングとトレーサビリティを両立する監査設計 と組み合わせると実務的です。
CLEAR_CONTEXTとCLEAR_ALL_CONTEXT
特定属性だけ消したい場合は CLEAR_CONTEXT、名前空間全体を消したい場合は CLEAR_ALL_CONTEXT を使います。これらも信頼済みパッケージから直接または間接的に呼ぶ必要があります。
-- app_ctx_pkg パッケージボディ内の実装例
PROCEDURE clear_tenant IS
BEGIN
DBMS_SESSION.CLEAR_CONTEXT('APP_CTX', NULL, 'TENANT_ID');
END;
PROCEDURE clear_login_context IS
BEGIN
DBMS_SESSION.CLEAR_ALL_CONTEXT('APP_CTX');
END;
接続プールでは、特定属性だけ消すより、リクエスト境界で名前空間全体をクリアする方が安全です。属性を追加したときにクリア漏れが起きにくくなります。
LIST_CONTEXTで現在値を確認する
開発時には DBMS_SESSION.LIST_CONTEXT で現在セッションのコンテキストを確認できます。本番では機微情報が含まれる可能性があるため、出力先やログ化には注意してください。
SET SERVEROUTPUT ON
DECLARE
l_contexts DBMS_SESSION.APPCTXTABTYP;
l_count NUMBER;
BEGIN
DBMS_SESSION.LIST_CONTEXT(l_contexts, l_count);
FOR i IN 1 .. l_count LOOP
DBMS_OUTPUT.PUT_LINE(
l_contexts(i).namespace || '.' ||
l_contexts(i).attribute || '=' ||
l_contexts(i).value
);
END LOOP;
END;
/
デバッグ出力の見方は DBMS_OUTPUTの使い方 も参考になります。
VPDとDBMS_RLSで使う
アプリケーションコンテキストは、VPDのポリシー関数でよく使われます。たとえば TENANT_ID をセッションに設定し、ポリシー関数が tenant_id = ... の条件を返すことで、SQLを書き換えずにテナント分離できます。
CREATE OR REPLACE FUNCTION f_order_rls(
p_schema IN VARCHAR2,
p_object IN VARCHAR2
) RETURN VARCHAR2
AS
l_tenant_id NUMBER;
BEGIN
l_tenant_id := TO_NUMBER(SYS_CONTEXT('APP_CTX', 'TENANT_ID'));
RETURN 'tenant_id = ' || TO_CHAR(l_tenant_id);
EXCEPTION
WHEN VALUE_ERROR THEN
RETURN '1 = 2';
END;
/
BEGIN
DBMS_RLS.ADD_POLICY(
object_schema => USER,
object_name => 'ORDERS',
policy_name => 'ORDERS_BY_TENANT',
function_schema => USER,
policy_function => 'F_ORDER_RLS',
statement_types => 'SELECT,UPDATE,DELETE',
update_check => TRUE
);
END;
/
VPDの実装は DBMS_RLSの使い方 でも詳しく扱っています。未設定時に 1 = 2 を返して閉じる設計にしておくと、コンテキスト設定漏れによる全件表示を避けやすくなります。
ACCESSED GLOBALLYとclient_id
通常のセッションローカルコンテキストでは、そのセッション内だけで値を参照します。一方、ACCESSED GLOBALLY を指定したグローバルアプリケーションコンテキストでは、SET_CONTEXT の username / client_id パラメータを使って値を管理できます。
CREATE OR REPLACE CONTEXT app_global_ctx
USING app_global_ctx_pkg
ACCESSED GLOBALLY;
CREATE OR REPLACE PACKAGE BODY app_global_ctx_pkg AS
PROCEDURE set_global_value(
p_client_id IN VARCHAR2,
p_tenant_id IN NUMBER
) IS
BEGIN
DBMS_SESSION.SET_CONTEXT(
namespace => 'APP_GLOBAL_CTX',
attribute => 'TENANT_ID',
value => TO_CHAR(p_tenant_id),
username => SYS_CONTEXT('USERENV', 'SESSION_USER'),
client_id => p_client_id
);
END;
END;
/
グローバルコンテキストは便利ですが、通常のWebアプリで安易に使うと追跡やクリアが難しくなります。まずはセッションローカルコンテキストで要件を満たせるか確認し、必要な場合だけ採用します。
よくある失敗
SET_CONTEXTを無名ブロックから直接呼んで失敗する
CREATE CONTEXT ... USING で指定したパッケージのコールスタック内から呼ばれていない可能性があります。必ず専用パッケージを通してください。
接続プールで前の利用者の値が残る
リクエスト開始時に上書きしているつもりでも、新しい属性を追加したときに漏れることがあります。リクエスト開始時と終了時に CLEAR_ALL_CONTEXT する方が安全です。
コンテキスト値を信用しすぎる
画面から渡されたテナントIDやロールをそのまま設定すると危険です。ログイン済みユーザーからサーバー側で所属や権限を引き直し、検証済みの値だけを設定します。
RESULT_CACHE関数でSYS_CONTEXTを読む
セッション依存値を読む関数に RESULT_CACHE を付けると、利用者ごとに結果が違う前提と衝突しやすくなります。関数キャッシュの注意点は FUNCTION RESULT_CACHEの使い方 も確認してください。
本番前チェックリスト
CREATE CONTEXT ... USING のパッケージ名が正しいか。SET_CONTEXT / CLEAR_CONTEXT を専用パッケージ経由にしているか。SET_IDENTIFIER やログで利用者・リクエストを追跡できるか。まとめ
DBMS_SESSION.SET_CONTEXT を使うと、PL/SQLやSQLから参照できるアプリケーションコンテキストへ、ログインユーザーやテナントIDなどの値を設定できます。ただし、重要なのは CREATE CONTEXT ... USING で指定した信頼済みパッケージを通して設定・クリアすることです。
接続プール環境では、リクエストごとに設定し、返却前に必ずクリアします。VPDや監査ログと組み合わせると強力ですが、未設定時の扱い、入力検証、機微情報の保存禁止、セッション依存関数との組み合わせまで含めて設計しましょう。

