普通用户不能直接执行存储过程靠的是权限隔离与执行上下文锁定:仅授予execute权限,同时撤销其对底层表、视图等的直接访问权,并通过with execute as绑定专用低权限账户(如proc_executor),确保过程始终以该账户权限运行,调用者无需任何表级权限。

普通用户不能直接执行存储过程,不是靠“禁止调用”实现的,而是靠权限隔离+执行上下文锁定——只给 EXECUTE 权限,同时切断其对底层表、视图、函数的直接访问能力。
只授 EXECUTE,不给任何表级权限
这是最基础也最容易被忽略的一环。很多 DBA 给了 GRANT EXECUTE ON dbo.usp_GetOrder TO app_user 就以为完事,结果发现 app_user 还能 SELECT * FROM Orders ——说明表权限没清理干净。
- 执行
REVOKE SELECT, INSERT, UPDATE, DELETE ON SCHEMA::dbo FROM app_user(如果之前误授过) - 检查是否属于
db_datareader或db_datawriter角色:SELECT dp.name FROM sys.database_role_members drm JOIN sys.database_principals dp ON drm.member_principal_id = dp.principal_id WHERE drm.role_principal_id = DATABASE_PRINCIPAL_ID('db_datawriter') - 若在结果中看到
app_user,必须立刻执行ALTER ROLE db_datawriter DROP MEMBER [app_user]
用 EXECUTE AS 显式限定执行身份
不加 EXECUTE AS,过程默认以调用者身份运行,权限随用户走;加 EXECUTE AS OWNER 又等于把 dbo 权限借出去。真正可控的做法是绑定一个低权限专用账户。
- 创建无登录用户:
CREATE USER proc_executor WITHOUT LOGIN - 只授予过程内真正需要的权限,比如:
GRANT SELECT ON dbo.OrderLog TO proc_executor - 建过程时绑定:
CREATE PROCEDURE usp_GetOrder WITH EXECUTE AS 'proc_executor' AS ... - 这样无论谁调用,过程都只用
proc_executor的权限干活,app_user自己连SELECT那张表的权限都不需要
跨库引用必须三段名 + 目标库单独授权
过程里写了 OtherDB.dbo.Config,不代表自动有权限——SQL Server 会逐层校验调用链上每一步的权限。漏掉任意一环,就会报错:The server principal "proc_executor" is not able to access the database "OtherDB" under the current security context.
- 目标库必须显式授权:
USE OtherDB; GRANT SELECT ON dbo.Config TO proc_executor - 对象引用必须带完整三段名:
OtherDB.dbo.Config,不能写成dbo.Config或Config - 确认
TRUSTWORTHY是 OFF(默认就是关的,别动)
MySQL / PostgreSQL 用户注意 DEFINER 和 SECURITY_TYPE
SQL Server 没有 DEFINER 模式,但 MySQL 和 PG 用户常在这里翻车:过程以高权限账户定义,导致调用者间接越权。
- MySQL:查
SECURITY_TYPE字段,确保是INVOKER而非DEFINER;若为DEFINER='root@%',必须重建为SQL SECURITY INVOKER - PostgreSQL:确认函数定义中无
SECURITY DEFINER;避免SET search_path TO admin_schema - 验证命令:
SELECT ROUTINE_NAME, SECURITY_TYPE FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_SCHEMA = 'your_db'
真正的限制不在“能不能 CALL”,而在于“CALL 之后它还能不能干别的事”。过程本身只是个壳,权限控制的关键点藏在调用者身份、执行上下文、依赖对象权限这三层嵌套里——少盯住一层,就可能留出后门。











