最可靠的是用视图+权限隔离实现字段级脱敏:PostgreSQL需建无敏感字段视图并禁用基表访问,MySQL需用SQL SECURITY DEFINER视图配合脱敏函数,同时通过数据库注释动态驱动脱敏规则。
PostgreSQL 中用 ROW SECURITY POLICY 动态屏蔽字段最可靠
直接在数据库层做字段级脱敏,比应用层拼 sql 或 orm 过滤更防绕过。关键不是“能不能隐藏”,而是“连 select * 都拿不到敏感列”。postgresql 12+ 的 row security policy 本身不支持字段级控制,得配合视图 + 策略组合实现:
- 先建一个不含敏感字段的视图,比如
CREATE VIEW user_public AS SELECT id, name, created_at FROM users; - 再对原表
users启用行级策略(ENABLE ROW LEVEL SECURITY),并让目标角色(如app_reader)只能通过视图访问 - 禁止该角色直接查基表:
REVOKE SELECT ON users FROM app_reader; - 视图本身不继承策略,所以它天然“过滤”了字段——这不是权限控制,是结构隔离
注意:视图字段名、类型必须显式声明,否则某些客户端(如 pgAdmin)可能因元数据缺失报错。
MySQL 8.0+ 用 CREATE VIEW + DEFINER 控制字段可见性
MySQL 没有原生字段级 RLS,但 DEFINER 机制能绕过权限检查,让视图以高权限账号执行,从而返回“已脱敏”的结果集。常见错误是直接给用户 SELECT 基表权限,导致绕过视图:
- 创建视图时必须指定
SQL SECURITY DEFINER,例如:CREATE ALGORITHM=UNDEFINED DEFINER=`admin`@`%` SQL SECURITY DEFINER VIEW user_safe AS SELECT id, name, SHA2(phone, 256) AS phone_hash FROM users; -
phone字段永远不出现在视图定义里,只暴露脱敏后的phone_hash - 确保目标用户只有
SELECT视图权限,且被明确拒绝基表权限:REVOKE SELECT ON mydb.users FROM 'app'@'%'; - 如果用
SQL SECURITY INVOKER,视图会按调用者权限执行,此时若用户有基表权限,仍可绕过
字段脱敏配置别写死在代码里,用数据库注释驱动
硬编码哪些字段要脱敏(比如在 Java 里写 if (field.equals("id_card")) return "***";)会导致每次加新敏感字段都要发版。更稳的做法是把规则下沉到数据库元数据:
- 给敏感字段加注释:
COMMENT ON COLUMN users.id_card IS 'sensitive:mask=***'; - 应用启动时查
pg_catalog.pg_description(PostgreSQL)或information_schema.COLUMNS(MySQL),提取带sensitive:标记的列 - 动态生成视图 SQL 或配置 ORM 的序列化白名单,避免人工维护遗漏
- 注释内容格式要统一,建议用键值对,比如
sensitive:mask=SHA2(?,256)或sensitive:hide=true
这个方案的坑在于:不同数据库注释存储位置不同,MySQL 的 COLUMN_COMMENT 是字符串截断的(最大 1024 字符),别塞太长的脱敏逻辑。
千万别用 GRANT SELECT (col1,col2) 当字段权限
PostgreSQL 和 MySQL 都支持列级授权语法,但实际效果极不可靠:
- PostgreSQL 的
GRANT SELECT (a,b) ON t TO u只控制“能否显式 SELECT 这些列”,不影响SELECT *—— 用户仍能拿到所有字段,只是查单列时报错 - MySQL 的列权限在
SELECT *下完全失效,只要用户有表级SELECT权限,*就包含所有列 - 两者都不阻止用户用
INSERT/UPDATE写入敏感字段,权限模型本就不是为脱敏设计的 - 真正需要的是“不可见”,不是“查单列报错”——这属于语义错配
字段脱敏的本质是数据投影控制,不是权限粒度细化。想靠 GRANT 搞定,等于用锁头管窗户,门还大开着。










