基于用户委托密钥生成存储SAS密钥时遇访问拒绝问题排查
我根据官方文档构建了SAS密钥,步骤如下:
从Azure AD获取OAuth Token。使用OAuth生成用户委托密钥。使用用户委托密钥生成SAS Token
但访问Blob路径https://storage.blob.core.windows.net/container/dir/test.csv时遇到错误:
Access Denied. (message: 'Access to persistent storage path 'https://storage.blob.core.windows.net/container/dir/test.csv' was denied .
已拥有rwdl权限,以下是可能的操作错误点及修正方案:
核心错误点排查
1. SAS Token包含冗余参数
用户委托SAS不需要sv(API版本)参数,skv已指定用户委托密钥的版本,多余的sv会导致签名不匹配。需从SAS Token构建代码中移除s"sv=$apiVersion"项。
2. Canonical Resource格式验证
确保canonicalResource严格遵循/blob/<存储账户名>/<容器名>格式,例如存储账户名为mystorage、容器名为mycontainer时,应写为:
val canonicalResource = "/blob/mystorage/mycontainer"
无需添加Blob的具体路径(如dir/test.csv),因为是容器级SAS。
3. 签名字符串的换行符问题
构建stringToSign时,必须使用Unix换行符(\n),不能包含回车符(\r)。可打印生成的stringToSign确认格式是否正确。
4. 签名的URL编码方式
URLEncoder.encode可能存在编码偏差,建议直接替换Base64字符串中的特殊字符,确保符合Azure要求:
val signatureBase64 = Base64.getEncoder.encodeToString(signatureBytes) val signatureEncoded = signatureBase64.replace("+", "%2B").replace("/", "%2F").replace("=", "%3D")
5. 用户委托密钥的服务类型验证
确认从XML获取的signedService值为b(Blob服务),若服务类型不匹配会直接导致权限被拒。
修正后的关键代码片段
移除冗余参数的SAS Token构建
val sasToken = Seq( s"sp=$permissions", s"st=$sasStart", s"se=$sasExpiry", s"skoid=$signedOid", s"sktid=$signedTid", s"skt=$signedKeyStart", s"ske=$signedKeyExpiry", s"sks=$signedService", s"skv=$signedVersion", s"spr=$protocol", s"sr=$resource", s"sig=$signatureEncoded" ).mkString("&")
正确的签名编码方式
val keyBytes = Base64.getDecoder.decode(keyValue) val mac = Mac.getInstance("HmacSHA256") mac.init(new SecretKeySpec(keyBytes, "HmacSHA256")) val signatureBytes = mac.doFinal(stringToSign.getBytes("UTF-8")) val signatureBase64 = Base64.getEncoder.encodeToString(signatureBytes) val signatureEncoded = signatureBase64.replace("+", "%2B").replace("/", "%2F").replace("=", "%3D")
额外检查项
- 确认生成用户委托密钥的OAuth Token对应的主体(用户/服务主体)拥有Storage Blob Data Contributor或更高数据权限(仅订阅/资源组权限无效)。
- 检查SAS生效时间:
sasStart为当前时间减5分钟,sasExpiry为加1小时,确保当前时间在有效期内。 - 验证Blob访问路径的存储账户名、容器名是否与代码中的配置完全一致。
内容的提问来源于stack exchange,提问作者DxG

