PL/SQLをマルチスキーマで運用すると、テーブル所有者、API所有者、接続ユーザー、バッチユーザーが分かれます。このとき AUTHID DEFINER と AUTHID CURRENT_USER を曖昧に使うと、開発環境では動くのに本番だけ権限不足になる、逆に本来見せたくないデータを参照できる、という事故につながります。
この記事では、DATA、APP、APP_USER のような分離を前提に、Definer RightsとInvoker Rightsの使い分け、ロールと直接GRANT、名前解決、Oracle 12c以降で重要な INHERIT PRIVILEGES、動的SQLの検証までを実務向けに整理します。
- 更新系・権限昇格系は
AUTHID DEFINERでAPI境界に閉じるのが基本です。 - 呼び出し元ごとに見えるデータを変えたい参照系は
AUTHID CURRENT_USERが候補になります。 - Definer Rightsではロールに頼らず、所有者へ直接GRANTします。
- Invoker Rightsでは呼び出し元の権限・名前解決・有効ロール・呼び出し経路を検証します。
- Oracle 12c以降では
INHERIT PRIVILEGES不足によるORA-06598も確認します。
AUTHID全般の基礎は AUTHIDと権限管理の設計、依存関係の確認は パッケージ間依存を可視化するメタデータ解析、監査と相関IDは ロギングとトレーサビリティを両立する監査設計 と合わせて読むと理解しやすくなります。
スキーマ分離の基本形
ここでは、永続データを持つ DATA、PL/SQL APIを持つ APP、外部アプリケーションから接続する APP_USER を分けて考えます。
- DATA: テーブル、ビュー、インデックスなどの所有者。直接ログインや直接更新は最小化します。
- APP: パッケージ、ビュー、APIを所有するスキーマ。DATAへの必要権限を直接受けます。
- APP_USER: アプリケーションや利用者が接続するスキーマ。APPの公開APIを実行します。
- OPS / BATCH: 運用・バッチ用ユーザー。必要なAPIだけを実行します。
-- 例示用。実運用ではパスワード、表領域、プロファイルを別途設計する。 CREATE USER data IDENTIFIED BY "******" QUOTA UNLIMITED ON users; CREATE USER app IDENTIFIED BY "******" QUOTA UNLIMITED ON users; CREATE USER app_user IDENTIFIED BY "******"; GRANT CREATE SESSION TO data, app, app_user; GRANT CREATE TABLE TO data; GRANT CREATE PROCEDURE, CREATE SYNONYM TO app; -- DATAスキーマ CREATE TABLE data.orders ( order_id NUMBER PRIMARY KEY, customer_id NUMBER NOT NULL, amount NUMBER(12,2) NOT NULL, created_at TIMESTAMP DEFAULT SYSTIMESTAMP NOT NULL ); -- Definer RightsのAPPパッケージが使う権限は直接GRANTする GRANT SELECT, INSERT, UPDATE ON data.orders TO app; -- API実行権限はAPPから接続ユーザーへ付与する -- 実体パッケージ作成後に実行する -- GRANT EXECUTE ON app.pkg_order_write TO app_user;
ロールは利用者の可視性を切り替えるには便利ですが、PL/SQLのコンパイルやDefiner Rightsでは直接GRANTが必要になります。マルチスキーマ設計では、「誰が所有するか」と「誰の権限で実行するか」を分けて考えます。
Definer RightsとInvoker Rightsの違い
AUTHID DEFINER は、パッケージ所有者の権限で実行されます。AUTHID CURRENT_USER は、呼び出し元のユーザーを基準に権限チェックと名前解決が行われます。ただし、ロール、静的SQL、動的SQL、呼び出し経路によって見え方が変わるため、単純に「Invokerならロールが全部使える」と覚えるのは危険です。
- Definer Rights: 更新系、管理系、権限をAPI内に閉じたい処理に向きます。
- Invoker Rights: 呼び出し元ごとの可視性や権限差を反映したい参照系に向きます。
- 静的SQL: コンパイル時と実行時の権限要件を分けて確認します。
- 動的SQL: 実行時の権限・名前解決・入力検証が重要です。
更新系はDefiner Rightsに閉じる
注文登録、顧客統合、締め戻し、データ補正のような更新系は、呼び出し元にテーブル権限を直接渡すより、APPのDefiner Rightsパッケージに閉じるほうが安全です。外部には業務APIだけを公開し、DATA表への直接更新を許可しない構成にします。
CREATE OR REPLACE PACKAGE app.pkg_order_write AUTHID DEFINER AS
PROCEDURE upsert_order(
p_order_id IN NUMBER,
p_customer_id IN NUMBER,
p_amount IN NUMBER
);
END pkg_order_write;
/
CREATE OR REPLACE PACKAGE BODY app.pkg_order_write AS
PROCEDURE upsert_order(
p_order_id IN NUMBER,
p_customer_id IN NUMBER,
p_amount IN NUMBER
) IS
BEGIN
MERGE INTO data.orders d
USING (
SELECT p_order_id AS order_id,
p_customer_id AS customer_id,
p_amount AS amount
FROM dual
) s
ON (d.order_id = s.order_id)
WHEN MATCHED THEN
UPDATE SET d.customer_id = s.customer_id,
d.amount = s.amount
WHEN NOT MATCHED THEN
INSERT (order_id, customer_id, amount)
VALUES (s.order_id, s.customer_id, s.amount);
END;
END pkg_order_write;
/
GRANT EXECUTE ON app.pkg_order_write TO app_user;
この構成では、APP_USER に DATA.ORDERS のUPDATE権限を渡す必要がありません。必要なのは APP.PKG_ORDER_WRITE の実行権限です。権限昇格を許す代わりに、API内部で入力検証、監査ログ、例外処理を必ず行います。
参照系はInvoker Rightsで可視性を委譲する
呼び出し元の部署、テナント、ロール、VPDポリシーによって見える範囲を変えたい場合、Invoker Rightsが候補になります。ただし、Invoker Rightsの中で参照するオブジェクト名は、呼び出し元側で解決できる形にします。
CREATE OR REPLACE PACKAGE app.pkg_order_read AUTHID CURRENT_USER AS
FUNCTION get_amount(p_order_id IN NUMBER) RETURN NUMBER;
END pkg_order_read;
/
CREATE OR REPLACE PACKAGE BODY app.pkg_order_read AS
FUNCTION get_amount(p_order_id IN NUMBER) RETURN NUMBER IS
v_amount NUMBER;
BEGIN
-- Invoker Rightsでは名前解決が呼び出し元に寄るため、完全修飾を基本にする。
SELECT amount
INTO v_amount
FROM data.orders
WHERE order_id = p_order_id;
RETURN v_amount;
END;
END pkg_order_read;
/
GRANT EXECUTE ON app.pkg_order_read TO app_user;
Invoker Rightsでシノニムに頼る場合、APP側だけでなく呼び出し元側でも名前解決できるかを確認します。PUBLIC SYNONYMは便利ですが、名前衝突や環境差の原因にもなります。大規模環境では、重要な参照は完全修飾名に寄せたほうが調査しやすくなります。
Oracle 12c以降のINHERIT PRIVILEGESを確認する
Invoker Rightsを使う記事で特に落としやすいのが、Oracle 12c以降の INHERIT PRIVILEGES です。Invoker Rightsユニットの所有者が、呼び出し元ユーザーの権限を継承できない場合、ORA-06598: insufficient INHERIT PRIVILEGES privilege が発生します。
-- APPがAPP_USERの権限を継承できるようにする例 GRANT INHERIT PRIVILEGES ON USER app_user TO app; -- より広い権限。安易に付けず、管理者ロールや運用方針に合わせて判断する。 -- GRANT INHERIT ANY PRIVILEGES TO app; -- 現在の継承権限を確認 SELECT grantee, owner, table_name, privilege FROM dba_tab_privs WHERE privilege = 'INHERIT PRIVILEGES' AND grantee = 'APP' ORDER BY owner, table_name;
INHERIT ANY PRIVILEGES は強い権限です。全体付与で解決する前に、対象ユーザー単位の GRANT INHERIT PRIVILEGES ON USER ... で足りるかを確認します。移行時や本番だけInvoker Rightsが失敗する場合、この権限差を疑います。
ロールと直接GRANTを分けてテストする
Definer Rightsではロールに頼らず、所有者へ直接GRANTします。Invoker Rightsでは呼び出し元の権限差を反映できますが、静的SQL、動的SQL、呼び出し元、呼び出し経路を分けてテストします。
-- 参照ロールを作り、APP_USERにだけ付与する例 CREATE ROLE r_read_orders; GRANT SELECT ON data.orders TO r_read_orders; GRANT r_read_orders TO app_user; -- APP_USERで実行できることを確認 CONNECT app_user/****** SELECT app.pkg_order_read.get_amount(1001) FROM dual; -- ロールを無効化して差分を見る SET ROLE NONE; SELECT app.pkg_order_read.get_amount(1001) FROM dual; -- Definer Rightsの更新APIは、APP_USERがDATA.ORDERSを直接更新できなくても実行可能 EXEC app.pkg_order_write.upsert_order(1001, 10, 500);
このテストで見たいのは、「どの権限を誰に渡すと動くか」ではなく、「想定していない権限でも動いてしまわないか」です。参照系はロール差、更新系はAPI実行権限だけで動くことを確認します。
名前解決とシノニムの落とし穴
Invoker Rightsでは、未修飾名の解決が呼び出し元側に寄るため、開発環境でAPPに作ったシノニムだけを見て安心すると、本番のAPP_USERで失敗することがあります。特に同名テーブルやPUBLIC SYNONYMがある環境では、想定外のオブジェクトを参照するリスクがあります。
- 重要なDATA表は
data.ordersのように完全修飾する - シノニムを使うなら環境ごとの差分を管理する
- PUBLIC SYNONYMは命名衝突と影響範囲を確認する
- Invokerユニットは複数ユーザーで実行テストする
動的SQLはQUALIFIED_SQL_NAMEで検証する
スキーマ修飾名を扱う場合、DBMS_ASSERT.SIMPLE_SQL_NAME ではなく DBMS_ASSERT.QUALIFIED_SQL_NAME を使います。DATA.ORDERS_TMP_IDX のような修飾名は、単純名ではないためです。
CREATE OR REPLACE PACKAGE app.pkg_safe_exec AUTHID DEFINER AS
PROCEDURE drop_temp_idx(p_table_name IN VARCHAR2);
END pkg_safe_exec;
/
CREATE OR REPLACE PACKAGE BODY app.pkg_safe_exec AS
PROCEDURE drop_temp_idx(p_table_name IN VARCHAR2) IS
v_index_name VARCHAR2(261);
BEGIN
-- 動的DDLはホワイトリストで対象を限定する
IF UPPER(p_table_name) NOT IN ('ORDERS') THEN
RAISE_APPLICATION_ERROR(-20000, 'not allowed target');
END IF;
v_index_name := 'DATA.' || UPPER(p_table_name) || '_TMP_IDX';
EXECUTE IMMEDIATE
'DROP INDEX ' || DBMS_ASSERT.QUALIFIED_SQL_NAME(v_index_name);
END;
END pkg_safe_exec;
/
動的SQLでは、オブジェクト名はバインドできません。そのため、識別子を受け取る設計自体をできるだけ避け、必要な場合はホワイトリスト、DBMS_ASSERT、監査ログをセットにします。値は文字列連結せず、必ずバインド変数を使います。
監査と相関IDを残す
Definer Rightsで権限昇格する操作は、誰が呼んだのかを追えるようにします。DBMS_APPLICATION_INFO、DBMS_SESSION.SET_IDENTIFIER、アプリケーションログ、Unified AuditingやFGAを組み合わせると、APP所有パッケージ経由の操作でも呼び出し元を説明しやすくなります。
BEGIN
DBMS_APPLICATION_INFO.SET_MODULE(
module_name => 'ORDER_API',
action_name => 'READ'
);
DBMS_SESSION.SET_IDENTIFIER('CID-20260606-0001');
-- APP_USERからAPPのAPIを呼ぶ
:amount := app.pkg_order_read.get_amount(1001);
END;
/
権限設計はセキュリティだけでなく、障害調査にも効きます。誰の権限で、どのAPIを経由して、どのデータへ触れたかを後から追えるようにしておきます。
デプロイ前の権限・依存チェック
マルチスキーマでは、デプロイ先で権限が1つ足りないだけでINVALIDや実行時エラーになります。リリース前に、直接GRANT、ロール、INHERIT PRIVILEGES、依存関係、INVALIDを確認します。
-- APPがDATAへ直接持つ権限 SELECT owner, table_name, privilege FROM all_tab_privs WHERE grantee = 'APP' AND owner = 'DATA' ORDER BY table_name, privilege; -- APP_USERが実行できるAPP API SELECT owner, table_name AS object_name, privilege FROM all_tab_privs WHERE grantee = 'APP_USER' AND owner = 'APP' AND privilege = 'EXECUTE' ORDER BY object_name; -- APPスキーマのINVALID SELECT object_type, object_name, status FROM all_objects WHERE owner = 'APP' AND status = 'INVALID' ORDER BY object_type, object_name; -- APPからDATAへの依存 SELECT owner, name, type, referenced_owner, referenced_name, referenced_type FROM all_dependencies WHERE owner = 'APP' AND referenced_owner = 'DATA' ORDER BY name, referenced_type, referenced_name;
依存の読み方や再コンパイル順序は、依存可視化とINVALID再コンパイルの記事で詳しく扱っています。権限不足はコンパイル時に出る場合と、Invoker Rightsの実行時に初めて出る場合があるため、代表ユーザーでの実行テストを必ず入れます。
運用チェックリスト
- 更新系APIはDefiner Rightsに閉じているか
- 参照系Invoker Rightsの名前解決を完全修飾または明示的なシノニムで管理しているか
- APPへのDATA権限は直接GRANTされているか
- Invoker Rightsで必要なロール差を複数ユーザーで検証しているか
INHERIT PRIVILEGESまたはINHERIT ANY PRIVILEGESの方針があるか- 動的SQLの識別子を
DBMS_ASSERTとホワイトリストで検証しているか - 権限昇格APIの呼び出し元を監査できるか
- 本番デプロイ前にINVALIDと依存関係を確認しているか
まとめ
マルチスキーマ環境のPL/SQL権限設計では、Definer RightsとInvoker Rightsを役割で分けることが重要です。更新系や権限昇格が必要な処理はDefiner Rightsに閉じ、呼び出し元ごとの可視性を反映したい参照系はInvoker Rightsを検討します。
ただし、Invoker Rightsでは名前解決、ロール、直接権限、Oracle 12c以降の INHERIT PRIVILEGES が絡みます。動的SQLは最終手段にし、使う場合は DBMS_ASSERT.QUALIFIED_SQL_NAME とホワイトリストで守ります。権限・依存・監査をセットで設計すれば、マルチスキーマでも保守しやすく、説明責任を果たせるPL/SQL APIにできます。

