MySQL:视图做了列脱敏,用户怎么还是能查原表?
这是最经典的翻车现场。你辛辛苦苦建了一个叫user_safe_view的视图,把salary给屏蔽了,可用户一查users原表,数据全裸奔。
要解决,必须做三件事:
- 显式回收用户对原表的查询权限:REVOKE SELECT ON mydb.users FROM 'app_user'@'%'
- 只给用户授权视图,不要顺手给原表:GRANT SELECT ON mydb.user_safe_view TO 'app_user'@'%'
- 创建视图时,坚决写上SQL SECURITY DEFINER。如果用了默认的DEFINER,执行时按调用者权限检查,很容易报ERROR 1449,直接把查询堵死。
另外,如果视图里用CASE WHEN role = 'HR' THEN salary ELSE NULL END这类逻辑做脱敏,务必确保role字段来自可信赖的权限表,而不是前端传参。否则,用户自己传个role = 'HR'就能改?那防线就是纸糊的。
PostgreSQL:视图里写CURRENT_USER,为什么查不到自己的数据?
很多人在这一步踩坑,因为PostgreSQL视图定义是静态的。CURRENT_USER在CREATE VIEW时就被求值为定义者用户(比如postgres),而不是查询时的登录用户。换句话说,你写的“当前用户”,在视图创建那一刻就“固化”了。
正确的做法是这样的:
- 在基表上启用RLS:ALTER TABLE payroll ENABLE ROW LEVEL SECURITY
- 然后创建一个行级安全策略:CREATE POLICY emp_salary_policy ON payroll FOR SELECT USING (employee_id = current_setting('app.user_id')::int)
- 视图只负责“投影”需要的字段,不要加过滤条件:CREATE VIEW hr_payroll_vw AS SELECT id, name, department FROM payroll
- 应用层连接数据库后,先执行SET app.user_id = '123',再查视图——RLS策略会自动生效,按employee_id把数据滤掉。
这个逻辑是:视图是“裁剪字段”,RLS是“裁剪行”。两层分开,分工明确。
SQL Server:视图里用USER_NAME()做行过滤,危险在哪?
USER_NAME()返回的是数据库用户名,不是登录名。在EXECUTE AS或者跨库查询的场景下,它可能错乱,甚至直接返回NULL。这种飘忽不定的变量,用来自动过滤数据,风险不小。
稳妥的做法有几个方向:
- 改用ORIGINAL_LOGIN(),它稳定返回实际登录名,或者用SESSION_CONTEXT(N'user_id'),但需要应用层提前通过sp_set_session_context设置。
- 优先启用SQL Server原生的RLS:CREATE SECURITY POLICY SalesFilter ADD FILTER PREDICATE Security.fn_securitypredicate(sales_rep_id) ON dbo.orders
- 安全函数里别写复杂逻辑,比如递归查组织树,这会严重影响执行计划下推。安全谓词必须轻量、可下推。
- 测试时,一定用真实业务账号去连,别用sa或dbo直接测——权限行为完全不同,测出来的结果跟线上是两码事。
最后再说一个很容易被忽略的点:视图定义脚本里如果写了函数,比如MD5(email)、SUBSTRING(phone, 1, 3),那对应字段的索引会被直接废掉。如果这个字段还要用在WHERE过滤上,查询性能会断崖式下跌。脱敏和查询效率,最好是分开设计——脱敏用独立字段或策略,别硬塞在同一个视图脚本里。
