You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Azure SQL中如何为存储过程授予调用用户不具备的权限

Azure SQL存储过程小时级执行频次限制方案

已完成的基础配置

  • 已在目标业务数据库(非master系统库)创建专属受限用户,创建语句如下:
CREATE USER sp_only_user WITH PASSWORD = 'blabla12345!@#$' 
  • 已仅为该用户授予目标存储过程sp_Get_Clients的执行权限:
GRANT EXECUTE ON OBJECT::sp_Get_Clients to sp_only_user

待解决的核心问题

需要在存储过程内部校验自身过去1小时的执行记录,可选实现方式共两种,要求不向普通调用用户授予系统性能视图、统计函数的直接访问权限,仅让存储过程本身具备对应查询能力:

  1. 直接在存储过程内查询系统性能视图统计最近执行时间,查询语句如下,禁止普通用户直接执行该语句:
SELECT 
    @LastExecutionTime=  PS.last_execution_time
FROM 
    sys.dm_exec_procedure_stats PS 
INNER JOIN 
    sys.objects o ON O.[object_id] = PS.[object_id] 
WHERE
    name = 'sp_Get_Clients'
  1. 将统计逻辑封装为自定义函数dbo.fn_CountSpExecutionsPerHour,返回指定存储过程的小时级执行次数,存储过程内调用语句如下:
SELECT
    @ExecutionCntPerHour = dbo.fn_CountSpExecutionsPerHour('sp_Get_Clients') 

需要确认两个核心点:

  • 是否可以为存储过程授予调用者本身不具备的访问权限
  • 是否存在更简便、可靠的存储过程小时级执行频次限制方案

解决方案

1. 跨权限访问实现:模块证书签名(无需给用户授予额外权限)

完全可以实现存储过程持有调用者不具备的权限,不要用EXECUTE AS OWNER这类上下文切换方案——该方案会让存储过程全程以高权限所有者身份运行,一旦存在SQL注入漏洞,会直接泄露高权限,安全风险极高。
官方推荐的标准实现是模块证书签名,仅给对应模块授予完成操作所需的最小权限,步骤如下:

  1. 在目标业务库创建自定义证书
CREATE CERTIFICATE SpExecAccessCert 
ENCRYPTION BY PASSWORD = 'CertPassword_2024!@#'
WITH SUBJECT = 'Certificate for procedure execution stats access',
EXPIRY_DATE = '2099-12-31';
  1. 给需要访问DMV的对象添加证书签名:如果选择直接在存储过程内写DMV查询,就给存储过程签名;如果选择封装统计函数的方案,就给统计函数签名(同Schema下的存储过程调用函数走所有权链,无需额外授权)
-- 给存储过程签名示例
ADD SIGNATURE TO OBJECT::sp_Get_Clients
BY CERTIFICATE SpExecAccessCert
WITH PASSWORD = 'CertPassword_2024!@#';
  1. 将证书映射为数据库用户,仅给该用户授予访问DMV必需的VIEW SERVER STATE权限
CREATE USER SpExecAccessCertUser FROM CERTIFICATE SpExecAccessCert;
GRANT VIEW SERVER STATE TO SpExecAccessCertUser;

配置完成后,普通用户执行存储过程时,签名模块会自动携带证书用户的权限查询DMV,用户本身没有任何直接访问系统视图、统计函数的权限,符合最小权限要求。

注意:后续如果修改了存储过程、统计函数的定义,需要重新执行签名步骤,否则签名会自动失效。


2. 生产环境推荐方案:自建执行日志表(不要依赖DMV做频次校验)

sys.dm_exec_procedure_stats的数据存储在执行计划缓存中,完全不适合做业务层的频次校验:SQL实例重启、Azure SQL故障转移、执行计划缓存淘汰、存储过程重编译都会清空该视图的统计数据,直接导致频次校验失效。
更可靠、实现更简单的方案是自行维护轻量执行日志表,全程走所有权链,不需要任何服务器级权限:

  1. 创建执行日志表
CREATE TABLE dbo.SpExecutionLog(
    SpName SYSNAME NOT NULL,
    ExecuteUser SYSNAME NOT NULL,
    ExecuteTime DATETIME2(0) NOT NULL DEFAULT(SYSUTCDATETIME()),
    INDEX IX_SpExecutionLog_Query (SpName, ExecuteUser, ExecuteTime)
);
  1. 在存储过程开头加入频次校验逻辑,原有业务逻辑放在校验之后即可
CREATE OR ALTER PROCEDURE dbo.sp_Get_Clients
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @ExecCnt INT;
    -- 记录本次执行
    INSERT INTO dbo.SpExecutionLog(SpName, ExecuteUser)
    VALUES('sp_Get_Clients', ORIGINAL_LOGIN());
    -- 统计当前用户近1小时执行次数
    SELECT @ExecCnt = COUNT(*)
    FROM dbo.SpExecutionLog
    WHERE SpName = 'sp_Get_Clients'
      AND ExecuteUser = ORIGINAL_LOGIN()
      AND ExecuteTime > DATEADD(HOUR, -1, SYSUTCDATETIME());
    -- 超过阈值直接终止,阈值可根据需求调整
    IF @ExecCnt > 1
    BEGIN
        RAISERROR('该存储过程每小时仅允许执行1次',16,1);
        RETURN;
    END
    -- 原有业务查询逻辑写在此处
    -- SELECT xxx FROM xxx
END
  1. 仅需给受限用户授予日志表的插入权限即可,因为存储过程和日志表同属dbo架构,走所有权链,存储过程内查询日志表不需要给用户授予SELECT权限,用户完全无法查看日志内容,符合安全要求:
GRANT INSERT ON dbo.SpExecutionLog TO sp_only_user;

可定期添加作业清理日志表的历史数据,避免表体积过大影响性能。

内容的提问来源于stack exchange,提问作者user1372603

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 19:27:29