必须显式写EXECUTE AS,因为默认EXECUTE AS CALLER模式使过程完全继承调用者权限,导致权限失控、所有权链失效、动态SQL易被注入;而EXECUTE AS OWNER或指定用户可强制限定执行上下文,确保权限隔离与安全边界。

为什么必须显式写 EXECUTE AS,而不是依赖默认行为
SQL Server 存储过程默认以 EXECUTE AS CALLER 模式运行——也就是说,过程里每条语句都用调用者的权限去检查。哪怕你只给了用户 EXECUTE 权限,他照样能执行 SELECT * FROM sys.tables 或拼接表名的动态 SQL,只要他自己有对应元数据或表权限。这不是功能,是风险。
不加 EXECUTE AS,就等于把过程逻辑完全暴露在调用者权限下,所有权链形同虚设。尤其当过程访问跨 schema 对象、调用其他过程、或含 EXEC(@sql) 时,权限边界极易被绕过。
EXECUTE AS OWNER 是最稳妥的起点
EXECUTE AS OWNER 让过程始终以创建者(通常是 dbo)身份执行,调用者只需 EXECUTE 权限,无需底层表的任何 SELECT/INSERT 权限。所有权链也只在此前提下可靠生效。
- 创建时必须显式声明:
CREATE PROCEDURE dbo.GetActiveOrders WITH EXECUTE AS OWNER AS ... - 所有被访问的对象(表、视图、函数)必须属于同一 owner(如全是
dbo),否则所有权链中断,报错类似The SELECT permission was denied on the object 'Orders' - 别用
EXECUTE AS SELF:它把创建时的用户名硬编码进过程定义,DBA 账户变更后过程直接失效 - 如果 owner 是
dbo,确保dbo用户本身不被普通用户直接登录或滥用
需要更细粒度控制?用 EXECUTE AS 'username'
当你不想让过程拥有 dbo 级权限,而是限定在仅能更新某几张表的几个字段时,该用专用低权限账户,比如 proc_executor。
- 账户必须已存在且是数据库级用户:
CREATE USER [proc_executor] FOR LOGIN [proc_executor]; - 过程内所有操作都受该用户权限约束——哪怕没授
SELECT,过程里就不能查表;这是可控限制,不是 bug - 声明方式:
CREATE PROCEDURE dbo.UpdateOrderStatus WITH EXECUTE AS 'proc_executor' AS ... - 注意调用链:如果这个过程又调用了另一个没设
EXECUTE AS的过程,执行上下文会回落到 caller,可能意外突破权限边界
常见错误和验证要点
授完权不代表跑得通。很多问题只在运行时报错,而非授权时。
- 报错
The SELECT permission was denied on the object 'sys.tables'?说明过程里查了系统视图,且没关掉CALLER模式,也没给用户授元数据权限 - 动态 SQL(
EXEC(@sql))在CALLER下完全继承调用者权限,极易被注入利用;换成OWNER或指定用户后,执行范围才被框定 - 测试必须用
EXECUTE AS USER = 'app_user'; EXEC [schema].[proc_name]; REVERT;,不能只看 GRANT 是否成功 - 跨 schema 访问(如
sales.Customers)会立即中断所有权链,除非两个 schema 的 owner 完全一致
真正容易被忽略的是:EXECUTE AS 不是“开个开关就万事大吉”,它要求整个调用链、所有被引用对象、甚至后续嵌套过程,都保持上下文一致。一环松动,整条链就断。

















