oracle不支持原生字段级访问控制,dbms_rls仅能实现行过滤;字段级控制需结合视图、case脱敏与权限隔离(如收回基表权限、仅授视图权限)协同实现。
oracle 本身不支持原生的字段级访问控制(field-level security),dbms_rls 策略只能过滤行(where 条件),不能隐藏或脱敏单个列。所谓“字段级控制”,实际是通过组合手段模拟实现,核心依赖 dbms_rls + 视图 + 动态列投影,而非 vpd 单独完成。
VPD 函数返回值不能直接控制列可见性
很多人误以为在 F_SELECT_DATA_SECURITY 中返回类似 'SELECT id, name FROM ...' 的字符串就能动态选列——这是错的。DBMS_RLS 的策略函数只允许返回一个 WHERE 子句片段(如 'status = ''ACTIVE''),它会被自动拼接到用户原始 SQL 的 WHERE 后面。任何试图返回列名、SELECT 或 FROM 的写法都会报 ORA-28113: policy predicate has error。
- 策略函数必须返回
VARCHAR2类型的纯条件表达式,且不能含 SQL 关键字(SELECT/AS/JOIN等) - 返回空字符串或
'1=1'表示“不限制行”,但不会影响列 - 若想屏蔽某列,必须让该列在查询结果中根本不出现在 SELECT 列表里——这只能靠视图或应用层重写 SQL
字段级效果必须靠视图 + VPD 联合实现
真实可行的做法是:把敏感列(如 salary、id_card)从基表移出,放入独立视图,并在视图定义中用 CASE 或 DECODE 控制是否返回明文。再对这个视图绑定 VPD 策略,实现“行过滤 + 列逻辑判断”双重控制。
- 例如创建视图
V_EMP_SECURED,其中salary列写为:CASE WHEN SYS_CONTEXT('USERENV', 'SESSION_USER') IN ('HR_ADMIN') THEN salary ELSE NULL END - 然后对
V_EMP_SECURED执行DBMS_RLS.ADD_POLICY,策略函数控制谁能查到哪些行(比如部门隔离) - 用户查视图时,既受行策略限制,又受列内 CASE 逻辑约束;普通用户看到
salary是NULL,HR_ADMIN 看到真实值 - 注意:视图上的策略对象名必须是视图名,不是基表名;否则策略不生效
权限绕过风险:用户可绕过视图直查基表
仅靠视图 + VPD 不足以防止字段泄露,因为只要用户有基表 SELECT 权限,就能绕过视图直接查原始列。必须同步收回基表权限,只授予视图权限。
- 执行
REVOKE SELECT ON emp FROM xxu01,再GRANT SELECT ON v_emp_secured TO xxu01 - 如果业务代码硬编码了基表名(如
SELECT * FROM emp),改用视图需同步修改所有 SQL,这点常被忽略 - DBA 可通过
ALL_TAB_PRIVS检查用户是否仍有基表权限:SELECT * FROM ALL_TAB_PRIVS WHERE GRANTEE = 'XXU01' AND TABLE_NAME = 'EMP' - 额外加固:对基表启用审计或触发器记录异常访问,但无法阻止已有权限的用户
替代方案:应用层做字段裁剪更可控
在复杂权限场景下(比如同一用户对不同客户数据可见字段不同),硬塞进数据库层会迅速失控。更健壮的做法是把字段级规则下沉到应用层:
- 将字段权限配置化(如 JSON 描述 “user:XXU01 → table:emp → fields:[id,name]”)
- ORM 查询前拦截 SQL,动态重写 SELECT 列表,去掉无权字段
- 数据库只负责行级策略(VPD)和基础对象权限(GRANT/REVOKE),不承担字段逻辑
- 优势:规则变更无需 DBA 介入、支持运行时热更新、避免视图爆炸(每种字段组合都要建一个视图)
真正难的从来不是“怎么写策略函数”,而是谁来维护那张不断膨胀的字段权限映射表,以及当 SELECT * 遇到新字段时,系统是否还能保持一致行为。











