不能靠触发器识别“非管理员用户”,必须由应用层显式传入身份标识再校验;CURRENT_USER()返回的是连接账号(如app@10.0.2.5),非真实操作人,不可信。

直接结论:不能靠触发器识别“非管理员用户”,必须由应用层显式传入身份标识,再在触发器里校验;否则 CURRENT_USER() 返回的是连接账号(如 app@10.0.2.5),不是真实操作人。
MySQL 中触发器读不到真实操作人
你写 CURRENT_USER() 或 USER(),拿到的永远是数据库连接池账号(比如 app_rw@10.0.2.5),不是调用接口的员工 ID 或角色。DBA 直连、ETL 工具导入、甚至 SQL 注入绕过应用层时,这个值都不可信。
- 真正可用的身份字段,必须由应用在会话开头设置:
SET @current_app_user = 'u12345'; SET @current_app_role = 'finance_admin'; - 触发器里要用
COALESCE(@current_app_role, 'untrusted')做 fallback,避免变量未设导致 NULL 判空失败 - 如果应用没设变量,
@current_app_role就是 NULL,COALESCE会退到默认值,但此时你已丢失身份上下文——这不是触发器的问题,是调用方失职
SQL Server 中用 UPDATE() + deleted/inserted 表判断字段是否真被改
UPDATE(username) 只表示语句里写了 SET username = ...,哪怕值没变(SET username = username)也会返回 true。要拦截“值变了”的修改,必须结合 deleted 和 inserted 表比对:
- 正确写法:
IF UPDATE(amount) AND EXISTS (SELECT 1 FROM inserted i JOIN deleted d ON i.id = d.id WHERE i.amount != d.amount) - 注意 NULL 比较:用
i.amount IS DISTINCT FROM d.amount(SQL Server 2022+)或手动写(i.amount != d.amount) OR (i.amount IS NULL AND d.amount IS NOT NULL) OR (i.amount IS NOT NULL AND d.amount IS NULL) - 别在触发器里查其他表(如 roles 表)——会拖慢所有 UPDATE,且可能引发死锁
PostgreSQL 更推荐用 RLS 而不是触发器做权限隔离
给应用用户直接授 UPDATE 权限,等于放弃控制权。PostgreSQL 的标准解法是收走 DML 权限,只开放带校验的函数:
- 建一个
update_employee_safe()函数,用SECURITY DEFINER模式,内部白名单校验可改字段,并写审计日志 - 撤掉应用用户对
employees表的UPDATE权限,只给EXECUTE权限 - 加 RLS 策略作第二道防线:
USING (current_user IN ('hr_admin', 'sysadmin')),让非授权用户连SELECT都看不到敏感列 - 函数里不能用
RETURNING直接返回新值——应用层拿不到结果,得额外SELECT一次
所有数据库共通的硬伤:触发器不防 DELETE/INSERT,也不防 SELECT
你只加了 BEFORE UPDATE 触发器,但攻击者可以:
- 用
DELETE + INSERT绕过字段级校验(尤其当主键允许重用时) - 用
TRUNCATE TABLE清空整表(触发器完全不触发) - 直查基表绕过视图层脱敏(SQL Server 的 DDM、PG 的 RLS 都只作用于 SELECT,和触发器无关)
所以最终防线从来不是单点触发器,而是权限最小化 + 应用层身份透传 + 审计日志闭环——触发器只是其中一环,且最容易因身份缺失而失效。

















