在对接 Azure Blob 存储时,共享访问签名(SAS)是最常用的临时授权手段。它允许在不暴露账户密钥的前提下,把某个容器或对象的读写权限限定在一段时间与特定操作上。不过实际开发中,服务端经常返回 403 错误,提示签名不匹配,这往往源于客户端构造签名字符串与服务端预期不一致。

一、SAS 令牌的核心原理
SAS 的本质是一段由账户密钥或用户委托密钥签名的查询参数。服务端收到请求后,会用相同的密钥、相同的签名字符串规则重新计算签名,再与请求中传来的 sig 参数比对。只有两边完全一致才放行。因此,任何参与签名的字段,包括资源路径、权限、起始时间、过期时间、IP 限制、协议,都必须两端相同。
签名字符串的构造有严格顺序。以服务 SAS 为例,它通常以账户名、权限、起始时间、过期时间、资源标识、IP、协议、版本等字段按换行符连接,最后用 HMAC-SHA256 以账户密钥为密钥加密,再做 Base64 编码。哪怕多一个空格或少一个换行,结果都会截然不同。理解这个底层逻辑,是后续排查错误的基础。
二、用 SDK 生成 SAS 令牌的示例
使用 Azure Storage Blob SDK for Python 可以大幅降低手工拼接出错的概率。下面示例展示如何基于共享密钥创建一个容器级别的 SAS,仅允许读权限并一小时后失效。
from datetime import datetime, timedelta
from azure.storage.blob import BlobServiceClient, generate_container_sas, ContainerSasPermissions
account_name = "myaccount"
account_key = "your_account_key"
container_name = "mycontainer"
# 构造权限与有效期
permission = ContainerSasPermissions(read=True, list=True)
start_time = datetime.utcnow()
expiry_time = start_time + timedelta(hours=1)
# 生成 SAS 令牌,注意 SDK 内部已处理签名字符串与编码
sas_token = generate_container_sas(
account_name=account_name,
container_name=container_name,
account_key=account_key,
permission=permission,
expiry=expiry_time,
start=start_time
)
print("?" + sas_token)
上述代码由 SDK 负责拼接签名字符串与计算 HMAC,开发者只需要关注业务参数。如果你使用用户委托 SAS,则要先通过 DefaultAzureCredential 获取委托密钥,再调用 generate_container_sas 并传入 user_delegation_key,其签名规则与共享密钥略有不同,但排查思路一致。
如果必须在服务端之外手工构造,比如前端直传场景由后端返回令牌,那么务必保证后端使用的字段顺序、时间格式(UTC、ISO 8601)、资源路径(不带域名、以斜杠开头)与官方文档完全吻合。我们建议优先用 SDK 生成,避免手误。
三、签名错误的常见排查路径
当遇到 SignatureDidNotMatch 时,首先核对 URL 编码。SAS 中的资源路径、权限等出现在查询参数里,如果容器名或 blob 名含有特殊字符,必须经过百分号编码。某些 HTTP 客户端会自动二次编码,导致服务端解码后字段变化,签名自然对不上。
其次检查起始斜杠与资源标识。签名字符串里的 canonicalized_resource 通常形如 /account/container/blob,必须以斜杠开头且包含账户名。很多错误是因为把 https:// 域名部分也写进去,或者漏掉账户名层级。可以用打印日志把最终参与签名的原始字符串输出,与服务端期望格式逐字比较。
时钟偏移问题
Azure 服务端对起始时间和过期时间有容忍窗口,通常几分钟。如果部署程序的机器时钟快于或慢于标准时间,而 SAS 的 start 设为当前时刻,请求可能落在服务端认为未生效的区间。最稳妥的做法是把 start 设为十五分钟前,expiry 设为一小时后,避开时钟漂移。
此外,版本号(sv 参数)也会影响签名算法。旧版与新版在字段顺序或编码细节上有差异。若服务端升级后突然报错,可尝试固定 sv 为当前 SDK 使用的版本,而不是留空让服务端默认。
协议与 IP 限制
若 SAS 中指定了 sip 或 sp 参数,客户端请求来源或协议必须与之一致。例如设置了仅允许 HTTPS,却用 HTTP 调用,或者设置了固定出口 IP,但容器实例用了动态 IP,都会造成拒绝。这类错误有时也表现为签名不对,因为受限字段本身参与签名。
建议初期调试时先不限制 IP 与协议,仅用最小权限和短时效验证签名本身,通过后再逐步加上约束条件,这样能快速定位是哪一类字段引发了不匹配。
四、对比手工与 SDK 方案的优劣
| 方式 | 优点 | 风险 |
|---|---|---|
| SDK 生成 | 签名规则内置,不易拼错;支持用户委托密钥 | 依赖特定语言包,体积较大 |
| 手工构造 | 无额外依赖,适合极简环境 | 字段顺序、编码易错,维护成本高 |
从稳定性角度看,SDK 方案几乎消除了低级拼写错误。手工构造仅推荐在无法引入依赖的受限环境使用,且应当配套单元测试,把生成的 SAS 用 Azure 官方校验工具回测。
无论哪种方式,都把 SAS 令牌视作敏感凭据。不要写进前端代码仓库,也不要设置过长有效期。结合存储账号的监控日志,可以追踪每一个 SAS 的使用情况,发现异常访问立即吊销。
五、总结性实践建议
排查 SAS 签名错误,核心方法是让客户端与服务端的签名输入完全一致。从编码、资源路径、时间窗口、受限字段四个角度逐一排除,基本能覆盖九成以上的案例。日常开发中养成用 SDK 生成、最小权限、短时效的习惯,可显著降低运维负担。
当问题依然无法解决时,开启存储账户的日志记录,对比失败请求中的 Authorization 头部与本地计算的签名字符串,往往能发现隐藏的不可见字符或换行差异。把排查过程标准化,团队内后续遇到同类故障就能从容应对。
Azure_Blob_StorageSAS_tokensignature_error修改时间:2026-07-31 15:18:31