使用 Azure Blob 存储和 JavaScript 创建用户委托 SAS 令牌

本文介绍如何在适用于 JavaScript 的 Azure Blob 存储客户端库 v12 中创建用户委托 SAS 令牌。 版本 2018-11-09 中引入的用户委派 SAS 可使用 Microsoft Entra 凭据进行保护,并仅在以下情况下受 Blob 服务支持:

  • 授予对现有容器的访问权限。
  • 授予创建、使用和删除 Blob 的权限。

若要创建用户委托 SAS,客户端必须有权调用 blobServiceClient.getUserDelegationKey 操作。 此操作返回的密钥用于签署用户委托 SAS。 必须为调用此操作的安全主体分配包含 Microsoft.Storage/storageAccounts/blobServices/generateUserDelegationKey/action 的 RBAC 角色。

向拥有 SAS 的客户端授予的权限是以下两类权限的交集:授予请求用户委托密钥的安全主体的权限,以及授予签名权限 (sp) 字段中的 SAS 令牌资源的权限。 如果通过 RBAC 向安全主体授予的权限未针对 SAS 令牌授予,则不会向尝试使用 SAS 访问资源的客户端授予该权限。

示例代码片段在 GitHub 中作为可运行 Node.js 文件提供。

包 (npm) | 示例 | API 参考 | 库源代码 | 反馈

用户委托 SAS 令牌的最佳做法

因为任何拥有 SAS 令牌的人都可以使用它来访问容器和 Blob,所以应使用最严格的权限定义 SAS 令牌,但仍允许该令牌完成所需任务。

SAS 令牌的最佳做法

在 Azure 云中使用 DefaultAzureCredential

若要向 Azure 进行身份验证,请设置托管标识(无需机密)。 通过使用此方法,代码就可以使用 DefaultAzureCredential

若要为 Azure 云设置托管标识,请执行以下操作:

  • 创建托管标识
  • 为标识设置适当的存储角色
  • 配置 Azure 环境以使用托管标识

完成这两项任务后,请使用 DefaultAzureCredential 而不是连接字符串或帐户密钥。 通过使用此方法,所有环境都可以使用完全相同的源代码,而无需在源代码中使用机密

在本地开发中使用 DefaultAzureCredential

在本地开发环境中,Azure 标识(用于登录 Azure 门户的个人或开发帐户)需要向 Azure 进行身份验证,才可在本地和云运行时中使用相同的代码。

容器:将所需依赖项添加到你的应用程序

包括创建容器 SAS 令牌所需的依赖项。

const {
    DefaultAzureCredential
} = require('@azure/identity');
const {
    ContainerClient,
    BlobServiceClient,
    ContainerSASPermissions,
    generateBlobSASQueryParameters,
    SASProtocol
} = require('@azure/storage-blob');

// used for local environment variables
require('dotenv').config();

容器:获取环境变量

创建容器 SAS 令牌至少需要 Blob 存储帐户名称和容器名称值:

// Get environment variables for DefaultAzureCredential
const accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME;
const containerName = process.env.AZURE_STORAGE_BLOB_CONTAINER_NAME;

使用 DefaultAzureCredential 创建 SAS

若要使用 DefaultAzureCredential 创建 SAS 令牌,需要执行以下概念步骤:

  • 设置 DefaultAzureCredential
    • 本地开发 - 使用个人标识并设置存储角色
    • Azure 云 - 创建托管标识
  • 使用 DefaultAzureCredential 通过 UserDelegationKey 获取用户委托密钥
  • 使用用户委托密钥通过 generateBlobSASQueryParameters 构建具有适当字段的 SAS 令牌

容器:使用 DefaultAzureCredential 创建 SAS 令牌

配置标识后,使用以下代码为现有帐户和容器创建“用户委托 SAS 令牌”:

// Server creates User Delegation SAS Token for container
async function createContainerSas() {

    // Get environment variables
    const accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME;
    const containerName = process.env.AZURE_STORAGE_BLOB_CONTAINER_NAME;

    // Best practice: create time limits
    const TEN_MINUTES = 10 * 60 * 1000;
    const NOW = new Date();

    // Best practice: set start time a little before current time to 
    // make sure any clock issues are avoided
    const TEN_MINUTES_BEFORE_NOW = new Date(NOW.valueOf() - TEN_MINUTES);
    const TEN_MINUTES_AFTER_NOW = new Date(NOW.valueOf() + TEN_MINUTES);

    // Best practice: use managed identity - DefaultAzureCredential
    const blobServiceClient = new BlobServiceClient(
        `https://${accountName}.blob.core.chinacloudapi.cn`,
        new DefaultAzureCredential()
      );

    // Best practice: delegation key is time-limited  
    // When using a user delegation key, container must already exist 
    const userDelegationKey = await blobServiceClient.getUserDelegationKey(
        TEN_MINUTES_BEFORE_NOW, 
        TEN_MINUTES_AFTER_NOW
    );

    // Need only list permission to list blobs 
    const containerPermissionsForAnonymousUser = "l";

    // Best practice: SAS options are time-limited
    const sasOptions = {
        containerName,                                           
        permissions: ContainerSASPermissions.parse(containerPermissionsForAnonymousUser), 
        protocol: SASProtocol.HttpsAndHttp,
        startsOn: TEN_MINUTES_BEFORE_NOW,
        expiresOn: TEN_MINUTES_AFTER_NOW
    };
 
    const sasToken = generateBlobSASQueryParameters(
        sasOptions,
        userDelegationKey,
        accountName 
    ).toString();

    return sasToken;
}

前面的服务器代码会创建值流,以便创建容器 SAS 令牌:

创建容器 SAS 令牌后,可以将其提供给将使用令牌的客户端。 随后,客户端可以使用该令牌列出容器中的 Blob。 客户端代码示例展示了如何以使用者身份测试 SAS。

容器:使用 SAS 令牌

创建容器 SAS 令牌后,请使用令牌。 若要示范如何使用 SAS 令牌,你可以:

  • 构造完整的 URL,其中包括容器名称和查询字符串。 查询字符串是 SAS 令牌。
  • 使用容器 URL 创建 ContainerClient
  • 使用客户端:在此示例中,通过 listBlobsFlat 列出容器中的 Blob。
// Client or another process uses SAS token to use container
async function listBlobs(sasToken){

    // Get environment variables
    const accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME;
    const containerName = process.env.AZURE_STORAGE_BLOB_CONTAINER_NAME;
    
    // Create Url
    // SAS token is the query string with typical `?` delimiter
    const sasUrl = `https://${accountName}.blob.core.chinacloudapi.cn/${containerName}?${sasToken}`;
    console.log(`\nContainerUrl = ${sasUrl}\n`);

    // Create container client from SAS token url
    const containerClient = new ContainerClient(sasUrl);

    let i = 1;

    // List blobs in container
    for await (const blob of containerClient.listBlobsFlat()) {
        console.log(`Blob ${i++}: ${blob.name}`);
    }    
}

Blob:将所需依赖项添加到你的应用程序

包括创建 Blob SAS 令牌所需的依赖项。

const {
    DefaultAzureCredential
} = require('@azure/identity');
const {
    BlockBlobClient,
    BlobServiceClient,
    BlobSASPermissions,
    generateBlobSASQueryParameters,
    SASProtocol
} = require('@azure/storage-blob');

// used for local environment variables
require('dotenv').config();

Blob:获取环境变量

创建 Blob SAS 令牌至少需要 Blob 存储帐户名称和容器名称的值:

const accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME;
const containerName = process.env.AZURE_STORAGE_BLOB_CONTAINER_NAME;

如果需要创建 Blob SAS 令牌,则需要具有 Blob 名称才可创建 SAS 令牌。 需要进行预先确定,例如一个随机 Blob 名称、用户提交的 Blob 名称或从应用程序生成的名称。

// Create random blob name for text file
const blobName = `${(0|Math.random()*9e6).toString(36)}.txt`;

Blob:使用 DefaultAzureCredential 创建 SAS 令牌

配置标识后,使用以下代码为现有帐户和容器创建“用户委托 SAS 令牌”:

// Server creates User Delegation SAS Token for blob
async function createBlobSas(blobName) {

    // Get environment variables
    const accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME;
    const containerName = process.env.AZURE_STORAGE_BLOB_CONTAINER_NAME;

    // Best practice: create time limits
    const TEN_MINUTES = 10 * 60 * 1000;
    const NOW = new Date();

    // Best practice: set start time a little before current time to 
    // make sure any clock issues are avoided
    const TEN_MINUTES_BEFORE_NOW = new Date(NOW.valueOf() - TEN_MINUTES);
    const TEN_MINUTES_AFTER_NOW = new Date(NOW.valueOf() + TEN_MINUTES);

    // Best practice: use managed identity - DefaultAzureCredential
    const blobServiceClient = new BlobServiceClient(
        `https://${accountName}.blob.core.chinacloudapi.cn`,
        new DefaultAzureCredential()
      );

    // Best practice: delegation key is time-limited  
    // When using a user delegation key, container must already exist 
    const userDelegationKey = await blobServiceClient.getUserDelegationKey(
        TEN_MINUTES_BEFORE_NOW, 
        TEN_MINUTES_AFTER_NOW
    );

    // Need only create/write permission to upload file
    const blobPermissionsForAnonymousUser = "cw"

    // Best practice: SAS options are time-limited
    const sasOptions = {
        blobName,
        containerName,                                           
        permissions: BlobSASPermissions.parse(blobPermissionsForAnonymousUser), 
        protocol: SASProtocol.HttpsAndHttp,
        startsOn: TEN_MINUTES_BEFORE_NOW,
        expiresOn: TEN_MINUTES_AFTER_NOW
    };
 
    const sasToken = generateBlobSASQueryParameters(
        sasOptions,
        userDelegationKey,
        accountName 
    ).toString();

    return sasToken;
}

前面的代码会创建值流,以便创建容器 SAS 令牌:

创建 Blob SAS 令牌后,可以将其提供给将使用令牌的客户端。 随后,客户端可以使用它上传 Blob。 客户端代码示例演示如何将 SAS 作为使用者进行测试。

Blob:使用 SAS 令牌

创建 Blob SAS 令牌后,请使用令牌。 若要示范如何使用 SAS 令牌,你可以:

  • 构造完整的 URL,其中包括容器名称、Blob 名称和查询字符串。 查询字符串是 SAS 令牌。
  • 使用容器 URL 创建 BlockBlobClient
  • 使用客户端:在本例中,使用 upload 上传 Blob。
// Client or another process uses SAS token to upload content to blob
async function uploadStringToBlob(blobName, sasToken, textAsString){

    // Get environment variables
    const accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME;
    const containerName = process.env.AZURE_STORAGE_BLOB_CONTAINER_NAME;

    // Create Url SAS token as query string with typical `?` delimiter
    const sasUrl = `https://${accountName}.blob.core.chinacloudapi.cn/${containerName}/${blobName}?${sasToken}`;
    console.log(`\nBlobUrl = ${sasUrl}\n`);

    // Create blob client from SAS token url
    const blockBlobClient = new BlockBlobClient(sasUrl);

    // Upload string
    await blockBlobClient.upload(textAsString, textAsString.length, undefined);    
}

另请参阅