你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn。
使用 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 令牌,但仍允许该令牌完成所需任务。
在 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.windows.net`,
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 令牌:
- 使用 DefaultAzureCredential 创建 BlobServiceClient
- 使用 blobServiceClient.getUserDelegationKey 操作创建 UserDelegationKey
- 使用密钥通过 generateBlobSASQueryParameters 创建 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.windows.net/${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.windows.net`,
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 令牌:
- 使用 DefaultAzureCredential 创建 BlobServiceClient
- 使用 blobServiceClient.getUserDelegationKey 操作创建 UserDelegationKey
- 使用密钥创建 SAS 令牌 字符串。 如果未在选项中指定 Blob 名称,则 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.windows.net/${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);
}