默认execute as caller会破坏权限隔离,因存储过程内操作全凭调用者权限执行,无法实现字段级或行为级控制;动态sql继承调用者权限易被注入,跨schema引用中断所有权链,且未设execute as的嵌套过程会导致权限链断裂。

必须显式用 EXECUTE AS 切换执行上下文,否则存储过程内所有操作都直接走调用者权限,字段级、行为级控制根本无从谈起。
为什么默认 EXECUTE AS CALLER 会破坏权限隔离
SQL Server 存储过程默认以 EXECUTE AS CALLER 模式运行,这意味着:SELECT * FROM sys.tables 这类语句能跑通,只要调用者有元数据权限;UPDATE Orders SET status = 'shipped' 也能执行,哪怕他本不该改 status 字段——因为权限检查发生在过程外,不是在字段或语句粒度上。
常见错误现象:The UPDATE permission was denied on the object 'Orders', column 'status' 很少出现,因为 SQL Server 不支持原生列级 UPDATE 权限校验(除非用视图+INSTEAD OF 触发器或行级安全策略),但更隐蔽的问题是:过程里拼了动态 SQL,结果调用者凭空获得了访问任意表的权限。
- 动态 SQL(如
EXEC(@sql))在CALLER模式下完全继承调用者权限,极易被参数注入利用 - 跨 schema 引用(如
sales.Customers)会立即中断所有权链,触发额外权限检查 - 过程若调用了另一个没设
EXECUTE AS的过程,权限控制就断在那一环
EXECUTE AS OWNER 是最简可靠的起点
用 EXECUTE AS OWNER 后,整个过程以创建者身份(通常是 dbo)运行,调用者只需 EXECUTE 权限,无需底层表的 SELECT/INSERT 等任何权限。所有权链生效的前提是:过程访问的所有对象(表、视图、函数)都属于同一个 owner(比如全是 dbo)。
示例声明:
CREATE PROCEDURE dbo.UpdateOrderAmount
WITH EXECUTE AS OWNER
AS
BEGIN
UPDATE Orders SET amount = @new_amount WHERE order_id = @id;
-- 调用者不需要 Orders 表的 UPDATE 权限
END
- 别用
EXECUTE AS SELF:它把创建时的 login 名字硬编码进定义,DBA 换人后 owner 变更会导致过程执行失败 - 如果 owner 是
dbo,确保dbo账户本身不被普通用户直接登录,否则高权限被绕过 - 过程里不能访问
sales.Customers这种跨 schema 对象,除非你也把salesschema 下对应表改成dboowner
用 EXECUTE AS 'username' 实现字段/行为级约束
当需要比 OWNER 更细的控制(比如“财务角色可改 amount,但不可改 status”),就得建一个专用执行账户,比如 proc_executor,只授予它对特定字段的 UPDATE 权限,且禁止交互式登录。
操作步骤:
- 先创建登录和用户:
CREATE LOGIN proc_executor WITH PASSWORD = '...'; CREATE USER proc_executor FOR LOGIN proc_executor; - 只授必要权限:
GRANT UPDATE(amount) ON dbo.Orders TO proc_executor;(注意:SQL Server 支持列级 GRANT,但仅限 DML,不支持列级 SELECT/DELETE) - 过程声明用:
CREATE PROCEDURE dbo.UpdateOrderAmount WITH EXECUTE AS 'proc_executor' AS ... - 过程内部所有语句都受
proc_executor权限约束——哪怕它没被授SELECT,你就不能在过程里先SELECT再UPDATE,这是可控限制,不是 bug
这种模式下,你甚至可以配合 IF 校验 + RAISE_ERROR 做业务逻辑拦截,比如检查当前用户角色是否匹配字段修改意图,再决定是否允许执行后续语句。
权限授予与维护的关键细节
给用户授的是 EXECUTE 权限,不是表权限。用 GRANT EXECUTE ON OBJECT :: dbo.UpdateOrderAmount TO [FinanceRole] 即可。不要给用户直接授表权限,否则绕过过程就失控了。
- 优先用数据库角色(如
FinanceRole)而非单个用户授予权限,人员变动时只需调整成员关系 - 避免混合使用
GRANT和DENY:一旦DENY出现,它永远压倒GRANT,调试困难 - 过程若含动态 SQL 或调用其他过程,务必确认被调用对象也设置了
EXECUTE AS,否则权限链在调用点断裂 - 测试时一定要用真实低权限账号连接,而不是用
sa或sysadmin账号——后者会掩盖所有问题
真正难的不是写那句 EXECUTE AS,而是把所有被访问对象的 owner、所有被调用过程的执行上下文、所有动态 SQL 的输入校验,全部串成一条不松动的链。漏掉任意一环,细粒度控制就塌一半。










