【PL/SQL】AUTHIDと権限管理の設計|DEFINER・CURRENT_USER・直接GRANTの実務チェック

【PL/SQL】プロシージャの権限管理とAUTHIDの設計ベストプラクティス PL/SQL

PL/SQLのプロシージャやパッケージは、単に処理をまとめるだけでなく、データベース権限の入口にもなります。AUTHID DEFINER で所有者権限として実行するのか、AUTHID CURRENT_USER で呼び出し者権限として実行するのかによって、名前解決、権限チェック、障害時の原因、セキュリティリスクが変わります。

この記事では、AUTHIDの詳細仕様そのものよりも、プロシージャやパッケージを本番公開する前に確認したい権限設計を中心に整理します。AUTHIDの内部動作を深掘りしたい場合は Invoker RightsとDefiner Rightsを使い切る完全ガイド、権限を分けた実装例は 権限分離とセキュリティパッケージ もあわせて確認してください。

この記事で扱うこと

  • AUTHID DEFINERAUTHID CURRENT_USER の使い分け
  • プロシージャ・パッケージ公開時の直接GRANT設計
  • ロール経由権限が原因で起きる ORA-01031 / PLS-00201
  • INHERIT PRIVILEGES 不足による ORA-06598
  • 動的SQL、スキーマ分離、監査、デプロイ時のチェック項目
スポンサーリンク

AUTHIDで何が変わるか

AUTHID は、ストアドPL/SQLユニットが発行するSQLの実行時の名前解決と権限チェックに影響します。Oracle公式ドキュメントでも、AUTHID は実行時の名前解決と権限チェックに関係し、DEFINER がデフォルト、CURRENT_USER がInvoker Rightsユニットであると説明されています。

DEFINER定義者権限。所有者スキーマの権限で実行され、API化・権限カプセル化に向きます。
CURRENT_USER呼び出し者権限。呼び出し者の権限やスキーマを前提に処理する共通ユーティリティに向きます。
デフォルト明示しなければ通常は AUTHID DEFINER です。
重要点コンパイル時と実行時で権限チェックの考え方が変わるため、ロール依存の設計は避けます。

まず結論:業務APIはDEFINER、共通部品はCURRENT_USER

実務では、表を直接触らせずにパッケージだけ公開したい業務APIは AUTHID DEFINER、呼び出し者の表や権限を使って動く共通ユーティリティは AUTHID CURRENT_USER を第一候補にします。ただし、どちらが常に安全という話ではありません。重要なのは「誰に何を直接GRANTするか」「動的SQLをどう制限するか」「呼び出し者の切り替わりをどう監査するか」です。

業務API表所有スキーマまたはAPIスキーマに権限を集約し、利用者には EXECUTE だけを付与します。
共通ユーティリティ呼び出し者ごとのオブジェクトを処理するなら CURRENT_USER を検討します。
管理処理高権限処理は入口を限定し、呼び出し元検証・監査ログ・最小権限をセットにします。
動的SQLAUTHIDに関係なく、識別子はホワイトリスト、値はバインド変数を使います。

DEFINER RightsでAPIを公開する基本形

表への直接権限をアプリユーザーへ渡したくない場合は、APIスキーマにパッケージを作り、アプリユーザーにはパッケージの EXECUTE だけを付与します。これにより、呼び出し側は表を直接更新できず、許可された手続きだけを実行できます。

definer-api.sql
-- TABLE_OWNER が表を所有している想定
CREATE TABLE table_owner.orders (
  order_id     NUMBER PRIMARY KEY,
  customer_id  NUMBER NOT NULL,
  amount       NUMBER NOT NULL,
  updated_at   DATE DEFAULT SYSDATE
);

-- API_OWNER に必要最小限の直接権限を付与
GRANT SELECT, INSERT, UPDATE ON table_owner.orders TO api_owner;

-- API_OWNER 側で業務APIを作成
CREATE OR REPLACE PACKAGE api_owner.order_api AUTHID DEFINER AS
  PROCEDURE upsert_order(
    p_order_id    IN NUMBER,
    p_customer_id IN NUMBER,
    p_amount      IN NUMBER
  );
END order_api;
/

CREATE OR REPLACE PACKAGE BODY api_owner.order_api AS
  PROCEDURE upsert_order(
    p_order_id    IN NUMBER,
    p_customer_id IN NUMBER,
    p_amount      IN NUMBER
  ) IS
  BEGIN
    MERGE INTO table_owner.orders d
    USING (
      SELECT p_order_id order_id,
             p_customer_id customer_id,
             p_amount 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,
                 d.updated_at  = SYSDATE
    WHEN NOT MATCHED THEN
      INSERT (order_id, customer_id, amount)
      VALUES (s.order_id, s.customer_id, s.amount);
  END upsert_order;
END order_api;
/

-- APP_USER には表権限ではなくAPI実行権限だけを付与
GRANT EXECUTE ON api_owner.order_api TO app_user;

この設計では、APP_USERTABLE_OWNER.ORDERS を直接更新できません。更新は必ず API_OWNER.ORDER_API を通るため、入力検証、監査、業務ルールを1か所に集められます。プロシージャ・ファンクション自体の設計観点は プロシージャ・ファンクション完全ガイド も参考になります。

ロールではなく直接GRANTが必要

PL/SQLの権限トラブルで多いのが「SQL DeveloperではSELECTできるのに、プロシージャにすると ORA-01031PLS-00201 になる」というケースです。原因の多くは、表権限がロール経由で付与されていることです。ストアドPL/SQLのコンパイルや実行では、ロール経由の権限を前提にしない設計が基本です。

direct-grant.sql
-- NG: ロール経由だけではPL/SQL内で不足することがある
GRANT SELECT ON table_owner.orders TO app_read_role;
GRANT app_read_role TO api_owner;

-- OK: PL/SQL所有者へ必要なオブジェクト権限を直接付与
GRANT SELECT, INSERT, UPDATE ON table_owner.orders TO api_owner;

-- 呼び出し側へはAPIのEXECUTEを直接付与
GRANT EXECUTE ON api_owner.order_api TO app_user;

ORA-01031 の切り分けは ORA-01031 完全ガイド、識別子未宣言や権限不足による PLS-00201PLS-00201の原因と解決方法 も関連します。権限エラーはSQL単体の問題に見えて、実際にはロール・スキーマ・AUTHIDの組み合わせで起きることが多いです。

CURRENT_USERを使う場面

AUTHID CURRENT_USER は、呼び出し者の権限や現在スキーマを前提に処理したいときに使います。典型例は、複数スキーマに同じ構造の表があり、共通パッケージを各スキーマのデータに対して動かしたいケースです。

invoker-utility.sql
CREATE OR REPLACE PACKAGE report_util AUTHID CURRENT_USER AS
  FUNCTION total_amount(p_from IN DATE, p_to IN DATE) RETURN NUMBER;
END report_util;
/

CREATE OR REPLACE PACKAGE BODY report_util AS
  FUNCTION total_amount(p_from IN DATE, p_to IN DATE) RETURN NUMBER IS
    v_total NUMBER;
  BEGIN
    SELECT NVL(SUM(amount), 0)
    INTO v_total
    FROM orders
    WHERE updated_at >= p_from
      AND updated_at <  p_to;

    RETURN v_total;
  END total_amount;
END report_util;
/

この例の orders は、実行時には呼び出し者側の名前解決に影響されます。そのため、呼び出し者スキーマに同じ名前・同じ構造の表やシノニムが必要になる場合があります。マルチスキーマ環境での考え方は マルチスキーマ環境での権限管理とInvoker Rights設計 と相性が良い論点です。

INHERIT PRIVILEGESとORA-06598

Invoker Rightsを使う場合、近年のOracleでは INHERIT PRIVILEGES も重要です。IRユニットの所有者が呼び出し者の権限を継承できない場合、実行時に ORA-06598 が発生することがあります。これは「表のSELECT権限があるか」とは別の観点で、呼び出し者の権限を継承してよいかを制御する仕組みです。

inherit-privileges.sql
-- APP_USER の権限を UTIL_OWNER が継承できるようにする例
GRANT INHERIT PRIVILEGES ON USER app_user TO util_owner;

-- より広い権限。付与先は慎重に限定する
GRANT INHERIT ANY PRIVILEGES TO util_owner;

INHERIT ANY PRIVILEGES は広い権限です。便利だからといって安易に付与すると、Invoker Rightsの安全性を損ないます。まずは対象ユーザー単位の GRANT INHERIT PRIVILEGES ON USER ... を検討し、監査対象に含めるのがよいです。

動的SQLを使う場合の注意

Definer Rightsで動的SQLを実行すると、強い権限を持つ所有者スキーマの文脈で危険なSQLが走る可能性があります。Invoker Rightsでも、呼び出し者の権限を使って想定外の表や列にアクセスするリスクがあります。AUTHIDの種類に関係なく、動的SQLは値をバインドし、識別子はホワイトリストで絞るのが基本です。

safe-dynamic-sql.sql
CREATE OR REPLACE PROCEDURE purge_audit_log(
  p_table_key IN VARCHAR2,
  p_days      IN NUMBER
) AUTHID DEFINER
IS
  v_table_name VARCHAR2(30);
BEGIN
  v_table_name :=
    CASE p_table_key
      WHEN 'LOGIN' THEN 'LOGIN_AUDIT_LOG'
      WHEN 'API'   THEN 'API_AUDIT_LOG'
      ELSE RAISE_APPLICATION_ERROR(-20010, 'Invalid table key')
    END;

  EXECUTE IMMEDIATE
    'DELETE FROM audit_owner.' || v_table_name ||
    ' WHERE created_at < SYSDATE - :days'
    USING p_days;
END;
/

動的SQLの安全な書き方は 動的SQLのセキュアな書き方完全ガイド で詳しく扱っています。特にDefiner Rightsのパッケージでは、外部入力をSQL識別子に使わない設計が重要です。

スキーマ分離の推奨構成

大きめの業務システムでは、表所有スキーマ、APIスキーマ、実行ユーザーを分けると権限境界が明確になります。すべてを1スキーマに詰め込むと、便利な反面、どのユーザーが何を直接触れるのか見えにくくなります。

TABLE_OWNER実体表、索引、制約を所有します。一般ユーザーへ直接権限を渡しません。
API_OWNER業務パッケージを所有し、必要最小限の表権限だけを直接付与されます。
APP_USERアプリケーション接続ユーザー。原則としてAPIの EXECUTE だけを持ちます。
BATCH_USER夜間処理や保守処理用。通常ユーザーと分けて監査しやすくします。
schema-separation.sql
-- TABLE_OWNER -> API_OWNER
GRANT SELECT, INSERT, UPDATE, DELETE ON table_owner.orders TO api_owner;

-- API_OWNER -> APP_USER / BATCH_USER
GRANT EXECUTE ON api_owner.order_api TO app_user;
GRANT EXECUTE ON api_owner.order_admin_api TO batch_user;

-- APP_USER に表権限を直接渡さない
-- GRANT SELECT ON table_owner.orders TO app_user;  -- 原則NG

呼び出し元検証と監査

Definer Rightsは権限をカプセル化できる一方で、公開した入口を誰でも呼べると危険です。必要に応じて、呼び出し元ユーザー、クライアント識別子、アプリケーションコンテキスト、許可ロールを確認し、監査ログへ残します。

caller-check.sql
CREATE OR REPLACE PACKAGE BODY api_owner.order_admin_api AS
  PROCEDURE assert_allowed IS
  BEGIN
    IF SYS_CONTEXT('USERENV', 'SESSION_USER') NOT IN ('BATCH_USER', 'OPS_USER') THEN
      RAISE_APPLICATION_ERROR(-20020, 'not allowed');
    END IF;

    IF SYS_CONTEXT('USERENV', 'CLIENT_IDENTIFIER') IS NULL THEN
      RAISE_APPLICATION_ERROR(-20021, 'client identifier is required');
    END IF;
  END;

  PROCEDURE close_month(p_yyyymm IN CHAR) IS
  BEGIN
    assert_allowed;
    -- monthly close logic
    NULL;
  END;
END;
/

接続プールを使うアプリケーションでは、DBユーザーは同じでもアプリ上の利用者は変わります。DBMS_SESSION.SET_IDENTIFIER やアプリケーションコンテキストを使い、誰の操作だったか追跡できる状態にしておくと障害調査と監査が楽になります。

ACCESSIBLE BYで呼び出し元を絞る

Oracleのバージョンや利用方針によっては、ACCESSIBLE BY でサブプログラムの呼び出し元を制限できます。すべてを公開パッケージ仕様に置くのではなく、内部用の処理は内部パッケージへ分け、呼び出し可能なユニットを限定すると公開面を小さくできます。

accessible-by.sql
CREATE OR REPLACE PACKAGE api_owner.order_internal
  AUTHID DEFINER
  ACCESSIBLE BY (api_owner.order_api)
AS
  PROCEDURE validate_amount(p_amount IN NUMBER);
END order_internal;
/

この指定は環境やバージョン差を確認したうえで使います。使えない環境でも、公開パッケージと内部パッケージを分けるだけで、レビュー時に「外部から呼ばせる処理か」を見分けやすくなります。

権限棚卸しに使うSQL

権限設計は作って終わりではありません。リリース後に不要なGRANTが増えたり、退役したAPIが残ったりします。定期的に依存関係と権限を棚卸しし、最小権限に戻す運用が必要です。

privilege-inventory.sql
-- 自スキーマが持つオブジェクト権限
SELECT owner, table_name, privilege, grantor
FROM user_tab_privs
ORDER BY owner, table_name, privilege;

-- 自スキーマのオブジェクトへ付与している権限
SELECT grantee, table_name, privilege
FROM user_tab_privs_made
ORDER BY grantee, table_name, privilege;

-- PL/SQLユニットのAUTHID確認
SELECT object_name, procedure_name, authid
FROM user_procedures
WHERE object_type IN ('PROCEDURE', 'FUNCTION', 'PACKAGE')
ORDER BY object_name, procedure_name;

-- 依存オブジェクト確認
SELECT name, type, referenced_owner, referenced_name, referenced_type
FROM user_dependencies
WHERE type IN ('PROCEDURE', 'FUNCTION', 'PACKAGE', 'PACKAGE BODY')
ORDER BY name, referenced_owner, referenced_name;

本番前チェックリスト

AUTHID業務APIはDefiner、共通部品はCurrent Userなど、意図を明示しているか。
直接GRANTPL/SQL所有者に必要な表権限がロールではなく直接付与されているか。
EXECUTE呼び出しユーザーには必要なパッケージだけ EXECUTE を付与しているか。
INHERIT PRIVILEGESInvoker Rightsで ORA-06598 が起きない権限設計になっているか。
動的SQL値はバインド、識別子はホワイトリストで固定しているか。
監査接続ユーザーだけでなく、アプリ利用者や処理IDを追跡できるか。
負のテスト権限がないユーザーで失敗することも確認しているか。
棚卸し不要なGRANT、退役API、過剰な管理権限が残っていないか。

よくある失敗

SQL単体では動くのにプロシージャ化すると失敗する

ロール経由の権限に依存している可能性が高いです。対象表への権限を、プロシージャ所有者へ直接GRANTしているか確認します。

CURRENT_USERにしたら別スキーマの表を見に行ってしまう

Invoker Rightsでは実行時の名前解決が重要です。未修飾の表名を使う場合、呼び出し者側に同名オブジェクトやシノニムがあるか、意図した構造かを確認します。

Definer Rightsに強い権限を持たせすぎる

Definer Rightsは便利ですが、所有者スキーマの権限を入口から使える設計です。公開するプロシージャを絞り、動的SQLを制限し、管理者向け処理は一般APIから分離します。

まとめ

PL/SQLの権限管理では、AUTHID DEFINERAUTHID CURRENT_USER の違いを知るだけでなく、どのスキーマへ何を直接GRANTするかが重要です。業務APIはDefiner Rightsで権限をカプセル化し、共通ユーティリティはInvoker Rightsで呼び出し者の文脈を使う、という基本方針から設計すると整理しやすくなります。

本番前には、ロール依存がないか、ORA-01031ORA-06598 の原因を潰せているか、動的SQLが安全か、監査できるか、不要な権限が残っていないかを確認します。AUTHIDは小さな句ですが、データベースの公開面を決める重要な設計要素です。