PL/SQLで業務APIを作るとき、すべてのユーザーに表権限を直接配る設計は危険です。一方で、すべてを高権限のDefiner Rightsパッケージに閉じ込めると、呼び出し者ごとの可視範囲や監査が曖昧になります。実務では、DATAスキーマとAPIスキーマを分け、読み取り・更新・監査・行レベル制御を分担させる設計が安定します。
この記事では、AUTHID CURRENT_USER、AUTHID DEFINER、直接GRANT、INHERIT PRIVILEGES、DBMS_SESSION.SET_CONTEXT、DBMS_RLS、動的SQL、監査ログを組み合わせたセキュリティパッケージ設計を整理します。AUTHIDの基礎は AUTHIDと権限管理の設計、Invoker / Definerの詳しい使い分けは Invoker RightsとDefiner Rights完全ガイド もあわせて確認してください。
- DATAスキーマとAPIスキーマを分ける理由
- Invoker RightsとDefiner Rightsの判断フロー
- 直接GRANTとロール無効化の注意点
INHERIT PRIVILEGESとORA-06598の考え方DBMS_SESSION.SET_CONTEXTとDBMS_RLSの連携- 動的SQL、監査ログ、負の権限テスト、本番前チェック
最初に結論:読み取りはInvoker、更新はSecure Definer
権限分離で迷ったら、まず読み取りはInvoker Rights、更新はSecure Definerを基本形にします。呼び出し者の可視範囲を反映したい検索・レポートは AUTHID CURRENT_USER、複数表を一貫して更新する業務処理は AUTHID DEFINER でカプセル化する、という分け方です。
DATAスキーマとAPIスキーマを分ける
まず、表や索引を持つDATAスキーマと、PL/SQL APIを公開するAPIスキーマを分けます。アプリケーションユーザーには、DATAスキーマの表を直接更新させず、APIスキーマのパッケージに対する EXECUTE を与えます。
スキーマ分離と直接GRANTの例
-- DATAスキーマ: 実データを保持 CREATE TABLE data.orders ( order_id NUMBER PRIMARY KEY, tenant_id VARCHAR2(30) NOT NULL, cust_id NUMBER NOT NULL, amount NUMBER(12,2) NOT NULL, status VARCHAR2(20) NOT NULL, created_at TIMESTAMP DEFAULT SYSTIMESTAMP ); -- APIスキーマへ必要最小限のオブジェクト権限を直接付与 GRANT SELECT, INSERT, UPDATE ON data.orders TO api; -- アプリユーザーにはAPIだけ実行させる GRANT EXECUTE ON api.order_api TO app_user;
ユーザー・権限・ロールの基本は Oracleユーザー・権限・ロール完全ガイド、権限不足の切り分けは ORA-01031 insufficient privilegesの原因と対処 と ORA-00942の原因と解決方法 が関連します。
Invoker Rightsの基本
AUTHID CURRENT_USER を指定したPL/SQLは、実行時の呼び出し者を基準に名前解決と権限チェックが行われます。呼び出し者ごとに見える表やシノニムを変えたい場合、または可視範囲をそのまま反映したいレポートAPIに向きます。
呼び出し者の権限で集計する例
CREATE OR REPLACE PACKAGE api.report_api
AUTHID CURRENT_USER
AS
FUNCTION total_amount(
p_from IN DATE,
p_to IN DATE
) RETURN NUMBER;
END;
/
CREATE OR REPLACE PACKAGE BODY api.report_api AS
FUNCTION total_amount(
p_from IN DATE,
p_to IN DATE
) RETURN NUMBER IS
v_total NUMBER;
BEGIN
SELECT SUM(amount)
INTO v_total
FROM data.orders
WHERE created_at >= p_from
AND created_at < p_to;
RETURN NVL(v_total, 0);
END;
END;
/
Invoker Rightsでは、呼び出し者側に必要な権限やシノニムがないと失敗します。「APIを実行できること」と「API内部で参照する表を読めること」は分けて確認します。
Definer Rightsで更新処理をカプセル化する
AUTHID DEFINER はデフォルトの動作です。API所有者の権限で処理するため、利用者に表の更新権限を渡さず、パッケージ実行権限だけで業務処理を提供できます。ただし、高権限の肩代わりになるため、入口で呼び出し元や操作範囲を検証します。
Secure Definerの更新API
CREATE OR REPLACE PACKAGE api.order_api
AUTHID DEFINER
AS
PROCEDURE approve_order(
p_order_id IN NUMBER,
p_tenant_id IN VARCHAR2
);
END;
/
CREATE OR REPLACE PACKAGE BODY api.order_api AS
PROCEDURE assert_caller IS
BEGIN
IF SYS_CONTEXT('USERENV', 'SESSION_USER') NOT IN ('APP_USER', 'BATCH_USER') THEN
RAISE_APPLICATION_ERROR(-20000, 'not allowed');
END IF;
END;
PROCEDURE approve_order(
p_order_id IN NUMBER,
p_tenant_id IN VARCHAR2
) IS
BEGIN
assert_caller;
UPDATE data.orders
SET status = 'APPROVED'
WHERE order_id = p_order_id
AND tenant_id = p_tenant_id
AND status = 'WAITING';
IF SQL%ROWCOUNT = 0 THEN
RAISE_APPLICATION_ERROR(-20001, 'order not found or not allowed');
END IF;
END;
END;
/
更新APIでは、呼び出し者だけでなく、テナントID、業務状態、変更可能範囲を必ず条件に入れます。表権限を隠しても、APIが何でも更新できる入口になってしまえば危険です。
判断フロー:InvokerかDefinerか
どちらを使うべきかは、処理の目的で決めます。以下のように考えると、大きく外しにくくなります。
AUTHID CURRENT_USER が候補です。ユーザー別シノニムやRLSと相性があります。AUTHID DEFINER が候補です。API内で入力検証と操作範囲制限を行います。マルチスキーマ環境での設計は マルチスキーマ環境での権限管理とInvoker Rights設計 も参考になります。
ロールではなく直接GRANTを確認する
Definer Rightsの名前付きPL/SQLでは、ロール経由の権限に頼れません。開発環境ではロールのおかげでSELECTできても、パッケージをコンパイル・実行すると失敗することがあります。必要なオブジェクト権限は、所有者やAPIスキーマへ直接GRANTします。
直接GRANTの確認SQL
-- 自分に直接付与されたオブジェクト権限
SELECT owner,
table_name,
privilege,
grantor
FROM user_tab_privs
ORDER BY owner, table_name, privilege;
-- APIスキーマに付与されている権限を確認
SELECT owner,
table_name,
privilege,
grantee
FROM all_tab_privs
WHERE grantee = 'API'
ORDER BY owner, table_name, privilege;
コンパイルや依存関係が絡む場合は 依存オブジェクトとINVALID再コンパイル も確認してください。権限不足が原因でINVALID化し、再コンパイルしても直らないケースがあります。
INHERIT PRIVILEGESとORA-06598
Invoker Rightsでは、呼び出し者の権限を実行側が利用するため、INHERIT PRIVILEGES の制御が関係します。環境によっては、呼び出し者の権限を継承できず ORA-06598 が発生することがあります。誰の権限を誰が継承してよいのかを明示的に管理します。
INHERIT PRIVILEGESの例
-- apiユーザーがapp_userの権限を継承してInvoker Rightsを実行できるようにする例 GRANT INHERIT PRIVILEGES ON USER app_user TO api; -- 逆に不用意な継承を避けたい場合は取り消す REVOKE INHERIT PRIVILEGES ON USER app_user FROM api;
INHERIT PRIVILEGES はセキュリティ境界そのものです。エラーを消すために広く付与するのではなく、どのAPI所有者にどの呼び出し者の権限継承を許すかを設計します。
セキュリティコンテキストでテナントを保持する
テナントID、部署、利用者区分などを毎回API引数で渡すと、漏れや改ざんの余地が増えます。接続時やリクエスト開始時に DBMS_SESSION.SET_CONTEXT でコンテキストへ格納し、SQL側では SYS_CONTEXT で参照する設計が有効です。
DBMS_SESSION.SET_CONTEXTの例
CREATE CONTEXT app_ctx USING api.ctx_pkg;
CREATE OR REPLACE PACKAGE api.ctx_pkg
AUTHID DEFINER
AS
PROCEDURE set_tenant(p_tenant_id IN VARCHAR2);
END;
/
CREATE OR REPLACE PACKAGE BODY api.ctx_pkg AS
PROCEDURE set_tenant(p_tenant_id IN VARCHAR2) IS
BEGIN
DBMS_SESSION.SET_CONTEXT(
namespace => 'APP_CTX',
attribute => 'TENANT_ID',
value => p_tenant_id
);
END;
END;
/
SELECT SYS_CONTEXT('APP_CTX', 'TENANT_ID') AS tenant_id
FROM dual;
セキュリティコンテキストの実装は DBMS_SESSION.SET_CONTEXTの使い方 に詳しくまとめています。
RLS / VPDで行レベル制御をDBへ寄せる
アプリ側で毎回 tenant_id = :tenant_id を書く運用は、漏れた瞬間に情報漏えいにつながります。行レベルの絞り込みをDBポリシーとして強制したい場合は、DBMS_RLS によるVPDを検討します。
DBMS_RLSポリシーの骨格
CREATE OR REPLACE FUNCTION api.order_policy(
p_schema IN VARCHAR2,
p_object IN VARCHAR2
) RETURN VARCHAR2
AS
BEGIN
RETURN 'tenant_id = SYS_CONTEXT(''APP_CTX'', ''TENANT_ID'')';
END;
/
BEGIN
DBMS_RLS.ADD_POLICY(
object_schema => 'DATA',
object_name => 'ORDERS',
policy_name => 'ORDERS_TENANT_POLICY',
function_schema => 'API',
policy_function => 'ORDER_POLICY',
statement_types => 'SELECT,UPDATE,DELETE',
update_check => TRUE
);
END;
/
PL/SQL側のDBMS_RLS実装は DBMS_RLSの使い方、Oracle全体のVPD設計は Oracle VPD完全ガイド が関連します。
動的SQLは値をバインドし、識別子をホワイトリスト化する
セキュリティパッケージで動的SQLを使う場合、値を文字列連結してはいけません。値はバインド変数、表名・列名・ソートキーのような識別子はホワイトリストで検証します。Invoker Rightsと動的SQLを組み合わせると、名前解決や権限チェックも呼び出し者に寄るため、意図しない参照先に注意します。
ホワイトリスト付き動的SQL
CREATE OR REPLACE FUNCTION api.count_by_status(
p_table_name IN VARCHAR2,
p_status IN VARCHAR2
) RETURN NUMBER
AUTHID DEFINER
AS
v_sql VARCHAR2(1000);
v_count NUMBER;
BEGIN
IF p_table_name NOT IN ('DATA.ORDERS', 'DATA.ORDER_ARCHIVE') THEN
RAISE_APPLICATION_ERROR(-20010, 'invalid table');
END IF;
v_sql := 'SELECT COUNT(*) FROM ' || p_table_name ||
' WHERE status = :status';
EXECUTE IMMEDIATE v_sql INTO v_count USING p_status;
RETURN v_count;
END;
/
動的SQLとバインド変数の詳細は 動的SQLとBIND変数の最適設計 を参照してください。
監査ログは本体処理と分ける
権限分離APIでは、誰が、どのAPIを、どのキーで、成功・失敗のどちらで実行したかを残します。本体処理がロールバックされても障害調査ログを残したい場合は、監査専用の自律トランザクションを検討します。
監査ログ用パッケージ
CREATE OR REPLACE PACKAGE api.audit_pkg
AUTHID DEFINER
AS
PROCEDURE write_log(
p_action IN VARCHAR2,
p_key IN VARCHAR2,
p_result IN VARCHAR2
);
END;
/
CREATE OR REPLACE PACKAGE BODY api.audit_pkg AS
PROCEDURE write_log(
p_action IN VARCHAR2,
p_key IN VARCHAR2,
p_result IN VARCHAR2
) IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO api.audit_log(
action_name,
key_value,
result_code,
session_user,
current_user,
client_identifier,
logged_at
) VALUES (
p_action,
p_key,
p_result,
SYS_CONTEXT('USERENV', 'SESSION_USER'),
SYS_CONTEXT('USERENV', 'CURRENT_USER'),
SYS_CONTEXT('USERENV', 'CLIENT_IDENTIFIER'),
SYSTIMESTAMP
);
COMMIT;
END;
END;
/
監査ログは便利ですが、業務データの更新を自律トランザクションに逃がすのは避けます。ログは調査用、本体更新は通常トランザクションで扱う、という役割分担が安全です。
権限テストは成功ケースだけでは足りない
セキュリティ設計では、実行できることだけでなく、実行できてはいけないことが拒否されることをテストします。直接表SELECTできない、別テナント行を更新できない、ロールだけではコンパイルできない、未許可ユーザーがAPIを実行できない、という負のテストを入れます。
負の権限テスト例
-- app_userでは表を直接読めないこと
CONNECT app_user/password
SELECT COUNT(*) FROM data.orders;
-- ORA-00942 または権限不足になることを確認
-- API経由なら許可範囲だけ処理できること
BEGIN
api.order_api.approve_order(
p_order_id => 1001,
p_tenant_id => 'TENANT_A'
);
END;
/
-- 別テナントは拒否されること
BEGIN
api.order_api.approve_order(
p_order_id => 2001,
p_tenant_id => 'TENANT_B'
);
END;
/
プロシージャやファンクション自体の設計は PL/SQLプロシージャ・ファンクション完全ガイド も参考になります。
避けたい設計
本番前チェックリスト
- DATAスキーマとAPIスキーマの責務が分かれている
- アプリユーザーにDATA表の不要な直接権限がない
- APIスキーマには必要最小限の直接GRANTだけがある
- 読み取りAPIと更新APIでInvoker / Definerの使い分け理由が説明できる
- Definer APIの入口で呼び出し元・テナント・業務状態を検証している
INHERIT PRIVILEGESの付与範囲を確認している- RLSやセキュリティコンテキストの初期化漏れをテストしている
- 動的SQLの値はバインド、識別子はホワイトリストになっている
- 成功テストだけでなく、拒否されるべき操作の負のテストがある
- 監査ログに呼び出し者、対象キー、結果、時刻が残る
まとめ
PL/SQLの権限分離は、DATAスキーマとAPIスキーマを分け、表へ直接触らせず、API経由に集約するところから始まります。読み取りはInvoker Rightsで呼び出し者の可視範囲を反映し、更新はSecure Definerで業務ルールと監査をカプセル化するのが実務的です。
そのうえで、直接GRANT、INHERIT PRIVILEGES、セキュリティコンテキスト、DBMS_RLS、動的SQLのホワイトリスト、負の権限テストを組み合わせます。最小権限を守るだけでなく、誰が何をできて、何をできないのかをテストで証明できる状態にすることが、セキュリティパッケージ設計のゴールです。

