必须靠角色感知表达式、显式权限隔离与边界值兜底处理三者同时到位;缺一不可。

VIEW 本身不脱敏,也不动态——它只是个命名的 SELECT。真正实现字段级动态掩码,必须靠三件事同时到位:**角色感知的表达式 + 显式权限隔离 + 边界值兜底处理**。缺一不可。
用 CASE WHEN + 角色函数判断用户身份
视图里不能写 WHERE role = 'admin',因为视图不认调用者是谁;也不能硬编码用户名,否则权限变更就得改视图。必须用数据库原生的角色校验函数:
- PostgreSQL:用
pg_has_role(current_user, 'sensitive_reader', 'member'),第三个参数必须是'member'(查成员资格),不是'usage'或空字符串 - SQL Server:用
IS_MEMBER('db_senior_analyst')返回1/0,比查USER_NAME()更符合 RBAC 原则 - MySQL 8.0+:没有内置角色函数,得自己建
user_roles表并LEFT JOIN,或用SUBSTRING_INDEX(USER(), '@', 1)提取用户名再关联映射
错误示范:CASE WHEN current_user = 'alice' THEN phone ELSE mask_phone(phone) END——账号重名、角色调整后立即失效。
脱敏表达式必须覆盖 NULL、空字符串和长度异常
直接写 CONCAT(LEFT(phone, 3), '****', RIGHT(phone, 4)) 在生产环境大概率崩:遇到 NULL 返回 NULL,遇到 ' ' 或 '138' 截断错位,前端渲染失败。
- PostgreSQL 示例:
CASE WHEN phone IS NULL OR phone = '' THEN '***' WHEN LENGTH(TRIM(phone)) = 11 THEN CONCAT(LEFT(TRIM(phone), 3), '****', RIGHT(TRIM(phone), 4)) ELSE '****' END AS phone_masked - MySQL 注意:
''和NULL要分开判,TRIM()不能省,否则空格导致LEFT截出空值 - JSON 字段如
meta需先提取再脱敏:meta->>'phone'(PG)或TRIM(BOTH '"' FROM JSON_EXTRACT(meta, '$.phone'))(MySQL)
权限回收比脱敏逻辑更重要
视图再严谨,只要用户还有基表的 SELECT 权限,就能绕过视图直查明文。这一步漏掉,前面所有工作归零。
- 必须执行:
REVOKE SELECT ON users FROM 'app_user'@'%'(PostgreSQL/MySQL 均适用) - 只授视图权限:
GRANT SELECT ON v_users_safe TO 'app_user'@'%' - 检查是否干净:
SHOW GRANTS FOR 'app_user'@'%',结果里不能出现ON users或ON *.* - 字段名必须加后缀,如
phone_masked,避免 ORM 或 BI 工具按元数据自动映射成原始字段
别在 WHERE 或 JOIN 中使用脱敏列
phone_masked 是计算列,数据库无法对其建索引。一旦写 WHERE phone_masked = '138****1234',全表扫描不可避免;写 JOIN ... ON o.phone_masked = u.phone_masked,结果不可控且性能极差。
- 真要按格式过滤,得回到原始字段:
WHERE phone LIKE '138%1234',或冗余加前缀字段phone_prefix CHAR(3) - BI 工具常默认开启字段筛选,需手动禁用
phone_masked的过滤能力 - 视图定义里严禁
SELECT *,漏掉一个敏感字段,脱敏就形同虚设
SQL SECURITY DEFINER(MySQL)或没确认 SET ROLE 是否激活(PostgreSQL),导致 current_user 拿到的是定义者而非调用者身份——掩码逻辑直接失效。

















