视图本身不脱敏,真正起作用的是“视图+权限隔离”组合;必须回收基表select权限、仅授予视图权限,并统一使用_masked后缀命名字段,同时对null、空值、长度异常做三层兜底处理。

视图本身不脱敏,它只是个查询封装;真正起作用的是“视图 + 权限隔离”这个组合。只建视图不收基表权限,等于在门上贴张“请勿入内”的纸条,门锁根本没装。
为什么直接用 CONCAT + LEFT 写视图会失效
很多人写完这样的视图就以为安全了:
CREATE VIEW v_users AS SELECT id, name, CONCAT(LEFT(phone, 3), '****', RIGHT(phone, 4)) AS phone FROM users;
问题不在函数,而在权限链路断裂:
- 用户只要还有
SELECT权限在users表上,就能绕开视图,直接执行SELECT phone FROM users -
CONCAT和LEFT是计算表达式,不触发任何权限检查,也不受数据库原生脱敏机制(如 SQL Server 的MASKED WITH)约束 - MySQL/PostgreSQL 根本没有列级动态脱敏能力,所谓“函数脱敏”只是静态字符串拼接,DBA 一眼看穿
必须显式回收基表权限并只授视图权限
建完视图后,这一步不能跳过,否则前面所有逻辑都白做:
- 执行
REVOKE SELECT ON users FROM 'app_user'@'%'—— 彻底切断直查通路 - 再执行
GRANT SELECT ON v_users TO 'app_user'@'%'—— 只允许走视图出口 - MySQL 8.0+ 推荐用角色:先
CREATE ROLE role_read_masked,再GRANT SELECT ON v_users TO role_read_masked,最后把角色授给用户,避免漏授或误授 - 检查是否残留权限:
SHOW GRANTS FOR 'app_user'@'%',确认结果里没有ON users字样
脱敏字段名必须加 _masked 后缀
别用和基表同名的字段名,这是最容易被忽略却后果最严重的设计点:
- 如果视图里定义了
phone列,而基表也有phone(明文),BI 工具或 ORM 会按元数据自动映射,导致应用层拿到脱敏值却当成原始格式使用 -
WHERE phone LIKE '138%'这类条件在视图里一定查不到数据,因为视图中的phone是计算列,还没执行到SELECT阶段时WHERE就已解析失败 - 正确做法是统一命名:用
phone_masked、id_card_masked、email_masked,让下游代码无法假装“这个字段还能当条件用” - PostgreSQL 创建含表达式的视图时,还必须显式声明列名:
CREATE VIEW v AS SELECT ... AS phone_masked,否则系统会报错
NULL、空值、长度异常必须三层兜底处理
LEFT(phone, 3) 看似简单,但实际运行中极易崩:
- 当
phone是NULL时,LEFT(NULL, 3)返回NULL,脱敏结果变成空,不是*** - 当
phone = ''或只有 2 位数字时,RIGHT(phone, 4)可能返回空或截断失败,导致拼接出类似138****这种畸形结果 - 正确写法必须显式覆盖边界:
CASE WHEN phone IS NULL THEN '***' WHEN TRIM(phone) = '' THEN '***' WHEN LENGTH(TRIM(phone)) = 11 THEN CONCAT(...) ELSE '****' END - 别依赖正则判断格式(如
REGEXP '^1[3-9]\d{9}$'),某些旧版 MySQL 不支持,且正则性能比长度判断差一个数量级
脱敏真正的复杂点从来不在函数怎么写,而在于权限是否切得干净、字段名是否暴露逻辑、以及 NULL 是否被当成正常值透传下去——这些地方一漏,整个脱敏策略就形同虚设。











