Oracle Virtual Private Database(VPD)

Oracle Virtual Private Database(VPD)

适用版本:Oracle Database 11g / 12c / 19c / 23ai 文档版本:v1.0 / 2026-07


1. 概述

Virtual Private Database(VPD,虚拟专用数据库) 是 Oracle 的行级安全控制机制[1]:

核心特性

  • 行级访问控制:基于用户上下文过滤行
  • 附加 WHERE 子句:自动添加到 SQL
  • 细粒度安全:表/视图/列级
  • 应用透明:无需修改 SQL

典型场景

  • 多租户数据隔离
  • 部门数据隔离
  • 行级权限控制

2. VPD 工作原理

2.1 工作流程

1. 用户执行 SQL: SELECT * FROM employees
2. VPD 策略触发
3. 策略函数返回谓词: dept_id = 10
4. SQL 改写: SELECT * FROM employees WHERE dept_id = 10
5. 执行改写后的 SQL
6. 仅返回 dept_id = 10 的行

2.2 组件

组件作用
Policy(策略)定义策略规则
Policy Function返回谓词的函数
Application Context上下文信息
Fine-Grained Access ControlFGAC 实现

3. 配置 VPD

3.1 创建 Application Context

-- 创建 context
CREATE OR REPLACE CONTEXT dept_ctx USING dept_pkg;

-- 创建包
CREATE OR REPLACE PACKAGE dept_pkg IS
  PROCEDURE set_dept_id;
END;
/

CREATE OR REPLACE PACKAGE BODY dept_pkg IS
  PROCEDURE set_dept_id IS
    v_dept_id NUMBER;
  BEGIN
    -- 根据用户名获取部门 ID
    SELECT dept_id INTO v_dept_id
    FROM user_dept
    WHERE username = sys_context('USERENV', 'SESSION_USER');
    
    -- 设置 context
    DBMS_SESSION.SET_CONTEXT('dept_ctx', 'dept_id', v_dept_id);
  END;
END;
/

3.2 创建登录触发器

-- 登录时自动设置 context
CREATE OR REPLACE TRIGGER set_dept_ctx_trg
  AFTER LOGON ON DATABASE
BEGIN
  dept_pkg.set_dept_id;
END;
/

3.3 创建策略函数

-- 策略函数返回谓词
CREATE OR REPLACE FUNCTION dept_policy_func(
  schema_name IN VARCHAR2,
  table_name IN VARCHAR2
) RETURN VARCHAR2 IS
  v_dept_id VARCHAR2(100);
BEGIN
  -- 获取 context 中的 dept_id
  v_dept_id := sys_context('dept_ctx', 'dept_id');
  
  -- 返回谓词
  IF v_dept_id IS NULL THEN
    RETURN '1=1';  -- 无限制
  ELSE
    RETURN 'dept_id = ' || v_dept_id;
  END IF;
END;
/

3.4 添加策略

BEGIN
  DBMS_RLS.ADD_POLICY(
    object_schema => 'scott',
    object_name => 'employees',
    policy_name => 'dept_policy',
    function_schema => 'sys',
    policy_function => 'dept_policy_func',
    statement_types => 'select, insert, update, delete',
    update_check => TRUE,
    enable => TRUE
  );
END;
/

3.5 测试

-- 用户 alice(dept_id=10)查询
SELECT * FROM scott.employees;
-- 实际执行: SELECT * FROM scott.employees WHERE dept_id = 10
-- 仅返回 dept_id=10 的行

-- 用户 bob(dept_id=20)查询
SELECT * FROM scott.employees;
-- 仅返回 dept_id=20 的行

4. 策略类型

4.1 动态策略(默认)

BEGIN
  DBMS_RLS.ADD_POLICY(
    ...
    policy_type => DBMS_RLS.DYNAMIC
  );
END;
/
-- 每次执行都调用策略函数

4.2 静态策略

BEGIN
  DBMS_RLS.ADD_POLICY(
    ...
    policy_type => DBMS_RLS.STATIC
  );
END;
/
-- 策略函数只调用一次,结果缓存

4.3 共享静态策略

BEGIN
  DBMS_RLS.ADD_POLICY(
    ...
    policy_type => DBMS_RLS.SHARED_STATIC
  );
END;
/
-- 多个对象共享策略

4.4 上下文敏感策略

BEGIN
  DBMS_RLS.ADD_POLICY(
    ...
    policy_type => DBMS_RLS.CONTEXT_SENSITIVE
  );
END;
/
-- context 变化时重新执行

4.5 共享上下文敏感策略

BEGIN
  DBMS_RLS.ADD_POLICY(
    ...
    policy_type => DBMS_RLS.SHARED_CONTEXT_SENSITIVE
  );
END;
/

5. 列级 VPD

5.1 列级策略

BEGIN
  DBMS_RLS.ADD_POLICY(
    object_schema => 'scott',
    object_name => 'employees',
    policy_name => 'salary_policy',
    function_schema => 'sys',
    policy_function => 'salary_policy_func',
    sec_relevant_cols => 'salary,bonus',           -- 敏感列
    sec_relevant_cols_opt => DBMS_RLS.ALL_ROWS     -- 显示所有行但敏感列置 NULL
  );
END;
/

5.2 策略函数

CREATE OR REPLACE FUNCTION salary_policy_func(
  schema_name IN VARCHAR2,
  table_name IN VARCHAR2
) RETURN VARCHAR2 IS
BEGIN
  -- 仅 HR 经理可查看薪资
  IF sys_context('USERENV', 'SESSION_USER') = 'HR_MANAGER' THEN
    RETURN NULL;  -- 无限制
  ELSE
    RETURN '1=0';  -- 敏感列置 NULL
  END IF;
END;
/

6. 策略管理

6.1 查看策略

SELECT 
  object_owner,
  object_name,
  policy_name,
  function_schema,
  policy_function,
  policy_type,
  enable
FROM dba_policies;

6.2 启用/禁用策略

-- 禁用
BEGIN
  DBMS_RLS.ENABLE_POLICY(
    object_schema => 'scott',
    object_name => 'employees',
    policy_name => 'dept_policy',
    enable => FALSE
  );
END;
/

-- 启用
BEGIN
  DBMS_RLS.ENABLE_POLICY(
    object_schema => 'scott',
    object_name => 'employees',
    policy_name => 'dept_policy',
    enable => TRUE
  );
END;
/

6.3 删除策略

BEGIN
  DBMS_RLS.DROP_POLICY(
    object_schema => 'scott',
    object_name => 'employees',
    policy_name => 'dept_policy'
  );
END;
/

6.4 刷新策略

-- 刷新所有策略
EXEC DBMS_RLS.REFRESH_GROUPED_POLICY;

-- 刷新指定策略
BEGIN
  DBMS_RLS.REFRESH_POLICY(
    object_schema => 'scott',
    object_name => 'employees',
    policy_name => 'dept_policy'
  );
END;
/

7. 分组策略

7.1 创建分组策略

BEGIN
  DBMS_RLS.CREATE_POLICY_GROUP(
    object_schema => 'scott',
    object_name => 'employees',
    policy_group => 'hr_pg'
  );
END;
/

-- 添加策略到分组
BEGIN
  DBMS_RLS.ADD_GROUPED_POLICY(
    object_schema => 'scott',
    object_name => 'employees',
    policy_group => 'hr_pg',
    policy_name => 'dept_policy',
    function_schema => 'sys',
    policy_function => 'dept_policy_func'
  );
END;
/

7.2 启用分组

-- 设置启用的分组
BEGIN
  DBMS_RLS.ENABLE_GROUPED_POLICY(
    object_schema => 'scott',
    object_name => 'employees',
    group_ctx => 'hr_pg'
  );
END;
/

8. 多租户与 VPD

8.1 PDB 中的 VPD

-- 切换到 PDB
ALTER SESSION SET CONTAINER = hrpdb;

-- 创建 VPD 策略(同上)
-- VPD 仅在当前 PDB 生效

8.2 Common VPD

VPD 策略不能跨 PDB,每个 PDB 独立配置。


9. 监控 VPD

9.1 策略查询

SELECT * FROM dba_policies WHERE object_name='EMPLOYEES';

9.2 Context 查询

-- 查看 context
SELECT namespace, attribute, value 
FROM session_context 
WHERE namespace='DEPT_CTX';

9.3 执行计划

-- 查看实际 SQL
EXPLAIN PLAN FOR SELECT * FROM scott.employees;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
-- 可见 WHERE 子句包含 VPD 谓词

10. 常见坑与排错

10.1 策略不生效

修复

-- 1. 检查策略是否启用
SELECT enable FROM dba_policies WHERE policy_name='DEPT_POLICY';

-- 2. 启用策略
BEGIN
  DBMS_RLS.ENABLE_POLICY(
    object_schema => 'scott',
    object_name => 'employees',
    policy_name => 'dept_policy',
    enable => TRUE
  );
END;
/

-- 3. 检查 context
SELECT * FROM session_context WHERE namespace='DEPT_CTX';

-- 4. 检查策略函数
SELECT dept_policy_func('scott','employees') FROM dual;

10.2 ORA-28113: 策略谓词错误

修复

-- 1. 检查策略函数返回值
SELECT dept_policy_func('scott','employees') FROM dual;

-- 2. 确保返回有效的 SQL 谓词
-- 例如: 'dept_id = 10'

10.3 Context 未设置

修复

-- 1. 检查登录触发器
SELECT trigger_name, status FROM dba_triggers WHERE trigger_name='SET_DEPT_CTX_TRG';

-- 2. 手动设置
EXEC dept_pkg.set_dept_id;

-- 3. 检查 context
SELECT sys_context('dept_ctx', 'dept_id') FROM dual;

10.4 SYS 用户绕过 VPD

原因:SYS 用户默认绕过 VPD。

修复

-- VPD 不影响 SYS/SYSDBA
-- 需要 VPD 的用户不能是 SYS

10.5 性能问题

修复

-- 1. 使用静态策略
BEGIN
  DBMS_RLS.ADD_POLICY(
    ...
    policy_type => DBMS_RLS.STATIC
  );
END;
/

-- 2. 确保谓词列有索引
CREATE INDEX idx_emp_dept ON employees(dept_id);

-- 3. 监控策略执行

11. 最佳实践

  1. 使用 Application Context:避免每次查询
  2. 登录触发器设置 context:自动化
  3. 策略列加索引:提升性能
  4. 静态策略优先:性能更好
  5. 列级 VPD:敏感数据保护
  6. 分组策略:复杂场景
  7. 监控策略执行:dba_policies
  8. 避免 SYS 用户:SYS 绕过 VPD
  9. PDB 中独立配置:多租户隔离
  10. 结合 Fine-Grained Auditing:审计

12. 参考资料

[1] Oracle Database Security Guide 19c, “Using Oracle Virtual Private Database” https://docs.oracle.com/en/database/oracle/oracle-database/19/dbseg/using-oracle-virtual-private-database.html