必须先显式回收基表select权限,再仅授予视图select权限;脱敏逻辑须硬编码在视图定义中,使用user()配合case when实现角色化脱敏,注意字段类型转换与null处理。

普通账号只能查特定表,必须先撤掉基表权限
只要用户对基表有 SELECT 权限,哪怕你建了脱敏视图,他也能绕过去直接查原表。这不是漏洞,是 MySQL 权限模型的默认行为。
常见翻车点:用户之前被授过 SELECT ON db_name.*,后来加了视图但忘了回收旧权限。务必先确认:
-
SHOW GRANTS FOR 'user'@'host';—— 查当前所有权限 -
REVOKE SELECT ON db_name.original_table FROM 'user'@'host';—— 显式收回基表权限 -
GRANT SELECT ON db_name.v_safe_view TO 'user'@'host';—— 只给视图权限
不需要 FLUSH PRIVILEGES;,GRANT/REVOKE 会自动生效。
脱敏逻辑必须写死在视图定义里,不能依赖参数
MySQL 视图不支持传参,所以像“按角色显示不同强度脱敏”这种需求,不能靠一个视图解决。如果真要动态判断,得用 USER() + CASE WHEN,但前提是视图必须带 DEFINER 和 SQL SECURITY DEFINER:
CREATE VIEW v_safe_user AS
SELECT
id,
CASE WHEN USER() = 'reporter'@'%' THEN name
ELSE CONCAT(LEFT(name, 1), '**') END AS name,
CONCAT(IFNULL(LEFT(phone, 3), ''), '****', IFNULL(RIGHT(phone, 4), '')) AS phone
FROM user_table;
注意:CURRENT_USER() 返回的是定义者身份,脱敏判断必须用 USER();IFNULL() 必须包住每一段字符串操作,否则 NULL 字段会让整列变 NULL。
字段类型不匹配会导致脱敏报错
手机号存成 BIGINT、金额是 DECIMAL、邮箱含 @ 符号——这些都会让 LEFT()、CONCAT() 直接报错或静默截断。
- 数字类字段(如
phone BIGINT):先CAST(phone AS CHAR)再处理 - 邮箱掩码用户名部分:不能只靠
SUBSTRING_INDEX(email, '@', 1),得嵌套LEFT(CAST(... AS CHAR), 2)配合RIGHT - 金额字段:必须
CAST(amount AS CHAR)后再拼接,否则会触发Truncated incorrect DOUBLE value
测试时一定要覆盖空字符串、全数字、带空格或括号的样本,比如 '(010) 1234-5678'。
视图无法拦截 SELECT * 或跨库 JOIN
这是最容易被忽略的限制:一旦用户在查询中写 SELECT * FROM v_safe_view,看起来没问题;但如果他在应用里拼了 JOIN other_db.raw_table,而他又恰好有那个库的权限,脱敏就完全失效。
更隐蔽的问题是:视图字段别名和原表字段名一致时,ORM 自动生成的 SELECT * 可能触发元数据缓存,导致客户端拿到的列信息还是原始名,后续逻辑误判。
真正安全的收口方式只有两个:要么用行级安全策略(MySQL 8.0+ RLS),要么在代理层(如 MaxScale、ProxySQL)做字段重写。视图只是最轻量、也最易漏的方案。











