视图本身不提供安全防护,必须配合权限回收才能生效;正确做法是显式定义字段、禁用select *、撤回基表权限、授予视图权限,并确保视图不可更新且脱敏字段重命名。

仅建视图不撤基表权限,等于没做任何防护。 视图本身不阻断访问,它只是查询模板;真正起作用的是权限回收动作——用户能直查 users 表,SELECT * 一次就全暴露了。
CREATE VIEW 必须显式列出字段,禁用 SELECT *
视图定义里出现 SELECT *,等于把基表所有列(包括后续新增的 internal_notes、salary_revision_date)全兜进来。哪怕你只在应用里查 name 和 email,元数据里照样暴露 password_hash 字段名和类型。
- 正确写法:
CREATE VIEW user_safe AS SELECT id, name, email, created_at FROM users; - 敏感字段如
ssn、credit_card_number从不出现,连别名都不写 - MySQL 中若基表加了新列,
SELECT *视图会自动包含它;显式列表则完全免疫 - PostgreSQL 要求含表达式时必须声明列名,比如
CREATE VIEW v AS SELECT email AS user_email FROM users;
GRANT 和 REVOKE 必须成对执行,缺一不可
只 GRANT SELECT ON user_safe TO app_user 不够,用户仍可用 SELECT ssn FROM users 直取。关键动作是砍掉基表权限链。
- 先执行:
REVOKE SELECT ON users FROM app_user;(PostgreSQL/MySQL 通用) - 再执行:
GRANT SELECT ON user_safe TO app_user; - PostgreSQL 额外要:
REVOKE USAGE ON SCHEMA public FROM app_user;,否则可能通过public.users显式引用绕过 - 验证方式:
SHOW GRANTS FOR 'app_user'@'%';,输出里不能出现ON mydb.users
别让视图变成可更新的“后门”
单表、无聚合、无表达式的视图,在 MySQL 和 PostgreSQL 中默认允许 UPDATE。用户虽看不到 is_deleted 列,但通过 UPDATE user_safe SET name = 'xxx' WHERE id = 1,可能意外恢复软删除记录。
- MySQL:默认可更新,加
WITH CHECK OPTION仅校验条件,不阻止写入 - PostgreSQL:查
pg_views.definition确认是否含聚合或表达式;如需只读,显式用CREATE VIEW ... AS SELECT ... WITH LOCAL CHECK OPTION并配合GRANT撤回INSERT/UPDATE - 最稳妥做法:不授写权限,且视图定义避开可更新结构(比如加
CASE WHEN ... END AS status_masked)
脱敏字段必须重命名,别留语义陷阱
写 CONCAT(LEFT(phone, 3), '****', RIGHT(phone, 4)) AS phone,列名还是 phone。前端代码可能直接拿这个值做短信校验,ORM 可能反向映射更新原字段,审计工具也识别不出这是掩码值。
- 强制后缀:
AS phone_masked、AS email_hashed,明确传递“非原始值”信号 - NULL 值要预处理:
CONCAT(LEFT(IFNULL(phone, ''), 3), '****', RIGHT(IFNULL(phone, ''), 4)) AS phone_masked,避免返回NULL导致空指针 - WHERE 过滤别依赖脱敏列:
WHERE phone_masked = '138****1234'必然全表扫描;真要过滤,改用WHERE phone LIKE '138%1234'并在 SELECT 里脱敏
最容易被跳过的环节不是语法,而是权限验证——连上数据库用目标账号执行 SELECT * FROM users; 和 SELECT * FROM user_safe;,亲眼确认前者报错、后者无敏感列,才算真正落地。











