Azure存储Blob用户委托SAS URL签名串含多余字符问题排查
基于User-Delegation的Azure Blob SAS URL签名不匹配问题排查
我们在TypeScript应用中创建基于User-Delegation的Azure存储Blob SAS URL时,遇到签名不匹配错误。生成的string-to-sign包含多余的换行符和"."字符,无法定位原因。我们无法手动配置签名串,必须通过正确编程方式解决。
代码实现
public async getStorageCredentials( tenant: string, subproject: string, bucket: string,readonly: boolean,partition: string): Promise<IAccessTokenModel> { const endpoint = await AzureDataEcosystemServices.getStorageEndpoint(partition); const now = new Date(); const expiration = this.addMinutes(now, SasExpirationInMinutes); const sasToken = await this.generateSASToken(endpoint, bucket, expiration, readonly); const result = { access_token: sasToken, expires_in: 3599, token_type: 'SasUrl', }; return result; } private async generateSASToken( endpoint: string, containerName: string, expiration: Date, readOnly: boolean): Promise<string> { const blobServiceClient = new BlobServiceClient( endpoint, this.defaultAzureCredential ); const accountName = blobServiceClient.accountName; const userDelegationKey = await this.getDelegationKey(blobServiceClient); const permissions = new ContainerSASPermissions(); permissions.list = true; permissions.write = !readOnly; permissions.create = !readOnly; permissions.delete = !readOnly; permissions.read = true; const containerSAS = generateBlobSASQueryParameters({ containerName, permissions, protocol: SASProtocol.Https, expiresOn: expiration }, userDelegationKey, // UserDelegationKey accountName); return `${endpoint}${containerName}?${containerSAS.toString()}`; } private async getDelegationKey(blobServiceClient: BlobServiceClient): Promise<UserDelegationKey> { const key = blobServiceClient.accountName; const now = new Date(); const cache = this.delegationKeyMap.get(key); if (cache && cache.expiration > now) { return cache.key; } const expiresOn = this.addMinutes(now, UserDelegationKeyValidityInMinutes); // Getting a key that is valid from ExpirationLeadInMinutes ago, in order to handle clock differences const response = await blobServiceClient.getUserDelegationKey( this.addMinutes(now, -ExpirationLeadInMinutes), expiresOn); // Expiring the key ExpirationLeadInMinutes before it stops being valid in order to handle clock differences const keyExpiration = this.addMinutes(expiresOn, -ExpirationLeadInMinutes); this.delegationKeyMap.set(key, { key: response, expiration: keyExpiration }); return response; }
签名串对比
期望的string-to-sign格式
rcwdl 2022-11-24T02:47:27Z /blob/<storage-account-name>/<container> 103adf72-21be-44fe-b948-c068ab79eb6 72f98bf-86f1-41af-91ab-2d7cd011db47 2022-10-22T03:17:01Z 2022-11-24T02:47:27Z b 2021-06-08 2021-06-08 c
实际生成的string-to-sign
rcwdl..2022-11-24T02:47:27Z./blob/<storage-account-name>/<container>.fbbcff7e-3f21-4eb4-adc4-80245934e61.33e0191-4d64-4f8c-a055-5bdaffd5e33d.2022-11-21T14:33:27Z.2022-11-24T02:47:27Z.b.2020-10-02.....https.2020-10-02.c......
报错信息
身份验证错误详情:签名不匹配。使用的签名串为 rcwdl...(完整内容如上述实际生成串)
问题根源及修复方案
缺失
startsOn参数
用户委托SAS要求必须指定生效时间startsOn,且该时间需与获取UserDelegationKey时的起始时间范围一致。代码中调用generateBlobSASQueryParameters未传入该参数,导致签名串出现空值占位的"."。- 修复:添加
startsOn参数,使用与获取密钥一致的起始时间:const now = new Date(); const startsOn = this.addMinutes(now, -ExpirationLeadInMinutes);
- 修复:添加
API版本不匹配
实际签名串中API版本为2020-10-02,与期望的2021-06-08不一致,导致签名逻辑差异。- 修复:初始化
BlobServiceClient时显式指定API版本:const blobServiceClient = new BlobServiceClient( endpoint, this.defaultAzureCredential, { serviceVersion: '2021-06-08' } );
- 修复:初始化
冗余
protocol参数
用户委托SAS默认强制HTTPS,无需显式指定protocol参数,该参数会导致SDK内部处理异常,生成多余占位符。- 修复:移除
protocol: SASProtocol.Https配置。
- 修复:移除
修改后的核心代码
private async generateSASToken( endpoint: string, containerName: string, expiration: Date, readOnly: boolean): Promise<string> { const blobServiceClient = new BlobServiceClient( endpoint, this.defaultAzureCredential, { serviceVersion: '2021-06-08' } ); const accountName = blobServiceClient.accountName; const userDelegationKey = await this.getDelegationKey(blobServiceClient); const now = new Date(); const startsOn = this.addMinutes(now, -ExpirationLeadInMinutes); const permissions = new ContainerSASPermissions(); permissions.list = true; permissions.write = !readOnly; permissions.create = !readOnly; permissions.delete = !readOnly; permissions.read = true; const containerSAS = generateBlobSASQueryParameters({ containerName, permissions, startsOn, expiresOn: expiration }, userDelegationKey, accountName); return `${endpoint}${containerName}?${containerSAS.toString()}`; }
内容的提问来源于stack exchange,提问作者Elizabeth
相关产品推荐
相关产品推荐

