带WITH EXECUTE AS OWNER的存储过程对sysadmin账户报错求助
我在VBA应用中使用以下存储过程让用户修改SQL Server数据库角色:
ALTER PROCEDURE [dbo].[usp_chgRole] @usr nvarchar(7), @db nvarchar(8), @oldrole nvarchar(7), @newrole nvarchar(7) WITH EXECUTE AS OWNER AS BEGIN SET NOCOUNT ON; DECLARE @sql nvarchar(max) --drop user from old role and add user to the new role SET @sql = ' EXEC ' + @db + '.dbo.sp_droprolemember ''' + @oldrole + ''',''' + @usr + '''' + EXEC ' + @db + '.dbo.sp_addrolemember ''' + @newrole + @usr + '''' EXEC (@sql) END
普通用户修改自身角色时存储过程运行正常,但用sysadmin账户登录后调用该存储过程修改其他用户的角色时,会报错:
服务器主体"<我的账户>"无法在当前安全上下文下访问数据库"<用户数据库>"。
移除WITH EXECUTE AS OWNER语句后,sysadmin账户可正常操作,但普通用户会报错。请问为什么拥有sysadmin权限的账户会出现这个报错?如何在不创建两个存储过程的前提下解决该问题?
ps. 在SSMS中调用带WITH EXECUTE AS OWNER的存储过程时也会出现相同报错。
当存储过程加上WITH EXECUTE AS OWNER后,不管调用者是谁,存储过程都会以存储过程所有者的身份执行,而非调用者本人的身份。
哪怕你是sysadmin(服务器级最高权限角色),进入这个执行上下文后,你的sysadmin权限会被覆盖。如果存储过程的所有者没有访问目标用户数据库的权限,自然就会弹出"无法访问数据库"的报错。
反过来,去掉WITH EXECUTE AS OWNER后,存储过程用调用者自己的身份执行:sysadmin本身有跨库操作的权限,所以能正常运行;但普通用户没权限修改别人的角色,因此会报错。
不用创建两个存储过程,只要在存储过程里动态判断调用者是不是sysadmin,再选择对应的执行方式即可:
- 删掉存储过程头部的
WITH EXECUTE AS OWNER,改为在内部根据权限切换执行上下文 - 用
IS_SRVROLEMEMBER('sysadmin')判断调用者是否为sysadmin角色成员 - sysadmin用户直接以自身身份执行动态SQL;普通用户则临时切换到OWNER身份执行,执行完成后恢复原上下文
同时注意:原动态SQL存在语法错误(sp_addrolemember参数拼接缺少逗号和引号),且未用QUOTENAME()处理数据库名,存在SQL注入风险,以下是修复并优化后的存储过程代码:
ALTER PROCEDURE [dbo].[usp_chgRole] @usr nvarchar(7), @db nvarchar(8), @oldrole nvarchar(7), @newrole nvarchar(7) AS BEGIN SET NOCOUNT ON; DECLARE @sql nvarchar(max) DECLARE @isSysadmin bit = IS_SRVROLEMEMBER('sysadmin') -- 修复语法错误并增加SQL注入防护 SET @sql = 'EXEC ' + QUOTENAME(@db) + '.dbo.sp_droprolemember ''' + @oldrole + ''',''' + @usr + '''; ' + 'EXEC ' + QUOTENAME(@db) + '.dbo.sp_addrolemember ''' + @newrole + ''',''' + @usr + ''';' IF @isSysadmin = 1 BEGIN -- sysadmin用户直接以自身身份执行 EXEC (@sql) END ELSE BEGIN -- 普通用户切换到OWNER身份执行,执行后恢复上下文 EXECUTE AS OWNER EXEC (@sql) REVERT END END
内容的提问来源于stack exchange,提问作者emphyrio

