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

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...(完整内容如上述实际生成串)


问题根源及修复方案

  1. 缺失startsOn参数
    用户委托SAS要求必须指定生效时间startsOn,且该时间需与获取UserDelegationKey时的起始时间范围一致。代码中调用generateBlobSASQueryParameters未传入该参数,导致签名串出现空值占位的"."。

    • 修复:添加startsOn参数,使用与获取密钥一致的起始时间:
      const now = new Date();
      const startsOn = this.addMinutes(now, -ExpirationLeadInMinutes);
      
  2. API版本不匹配
    实际签名串中API版本为2020-10-02,与期望的2021-06-08不一致,导致签名逻辑差异。

    • 修复:初始化BlobServiceClient时显式指定API版本:
      const blobServiceClient = new BlobServiceClient(
          endpoint,
          this.defaultAzureCredential,
          { serviceVersion: '2021-06-08' }
      );
      
  3. 冗余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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 15:45:41