适用于 JavaScript 的 Azure 标识库的身份验证最佳做法

本文提供指南,帮助你在向 Azure 服务进行身份验证时最大程度地提高 JavaScript 和 TypeScript 应用的性能和可靠性。 若要充分利用适用于 JavaScript 的 Azure 标识库,请务必了解潜在问题和缓解技术。

在生产环境中使用确定性凭据

DefaultAzureCredential 是开始使用 Azure 标识库的最方便方法,但这种便利性也引入了一些利弊。 最值得注意的是,无法提前保证链中将会成功并用于请求身份验证的特定凭证。 在生产环境中,这种不可预测性可能会引发有时是微妙的重大问题。

例如,请考虑以下假设的事件序列:

  1. 组织的安全团队要求所有应用都使用托管标识对 Azure 资源进行身份验证。
  2. 几个月来,托管在 Azure 虚拟机(VM)上的 JavaScript 应用成功用于 DefaultAzureCredential 通过托管标识进行身份验证。
  3. 开发人员无需告知支持团队,便可在该 VM 上安装 Azure CLI,并运行 az login 命令向 Azure 进行身份验证。
  4. 由于 Azure 环境中的这种新的单独配置更改,通过原始托管标识进行身份验证会意外开始以无提示方式失败。
  5. DefaultAzureCredential 跳过失败的 ManagedIdentityCredential,并搜索下一个可用的凭据,即 AzureCliCredential
  6. 应用程序开始使用 Azure CLI 凭据,而不是托管标识,这可能会失败或导致意外提升或减少特权。

为了防止在生产应用中出现此类细微问题或无提示故障,请将 DefaultAzureCredential 替换为特定的 TokenCredential 实现,例如 ManagedIdentityCredential。 有关可用凭据,请参阅 Azure 标识客户端库文档

例如,请考虑 Express.js 项目中的以下 DefaultAzureCredential 配置:

import { DefaultAzureCredential } from "@azure/identity";
import { SecretClient } from "@azure/keyvault-secrets";
import { BlobServiceClient } from "@azure/storage-blob";

const credential = new DefaultAzureCredential();

const secretClient = new SecretClient("https://keyVaultName.vault.azure.net", credential);
const blobServiceClient = new BlobServiceClient(
  "https://storageAccountName.blob.core.windows.net",
  credential
);

修改上述代码,根据运行应用的环境选择凭据:

import { AzureDeveloperCliCredential, ManagedIdentityCredential, ChainedTokenCredential, 
         AzureCliCredential } from "@azure/identity";
import { SecretClient } from "@azure/keyvault-secrets";
import { BlobServiceClient } from "@azure/storage-blob";

let credential;

// In production, use only ManagedIdentityCredential
if (process.env.NODE_ENV === 'production') {
  // For user-assigned managed identity, provide the client ID
  credential = new ManagedIdentityCredential(process.env.AZURE_CLIENT_ID);
}
// In development, use a chain of credentials appropriate for local work
else {
  credential = new ChainedTokenCredential(
    new AzureCliCredential(),
    new AzureDeveloperCliCredential()
  );
}

// Initialize Key Vault client
const secretClient = new SecretClient("https://keyVaultName.vault.azure.net", credential);

// Initialize Blob Storage client
const blobServiceClient = new BlobServiceClient(
  "https://storageAccountName.blob.core.windows.net",
  credential
);

在此示例中,ManagedIdentityCredential 仅用于生产。 然后,本地开发环境的身份验证需求由子句中 else 定义的凭据序列提供服务。

重用凭据实例

尽可能重复使用凭据实例以提高应用复原能力,并减少发给 Microsoft Entra ID 的访问令牌请求数。 重复使用凭据时,会尝试从基础 MSAL 依赖项所管理的应用令牌缓存中提取令牌。 如需了解更多信息,请参阅 Azure 标识客户端库中的 令牌缓存

令牌缓存行为在浏览器和 Node.js 环境之间有所不同。 在 Node.js 应用程序中,令牌默认缓存在内存中,这意味着当应用程序重启时缓存会丢失。 在浏览器应用程序中,令牌可以保留在浏览器存储(localStoragesessionStorage)中,具体取决于身份验证流和配置。 在为不同的应用程序类型实施凭据重用策略时,了解这些差异非常重要。

重要

不重复使用凭据的高容量应用可能会遇到来自 Microsoft Entra ID 的 HTTP 429 限制响应,这可能会导致应用故障。

建议的凭据重用策略因应用程序框架而异。

若要在 JavaScript 应用程序中实现凭据重用,请创建一个凭据实例,并在所有客户端对象中重复使用它:

import { DefaultAzureCredential, ManagedIdentityCredential } from "@azure/identity";
import { SecretClient } from "@azure/keyvault-secrets";
import { BlobServiceClient } from "@azure/storage-blob";

// Create a single credential instance
const credential = process.env.NODE_ENV === 'production'
  ? new ManagedIdentityCredential(process.env.AZURE_CLIENT_ID)
  : new DefaultAzureCredential();

// Reuse the credential across different client objects
const secretClient = new SecretClient("https://keyVaultName.vault.azure.net", credential);
const blobServiceClient = new BlobServiceClient(
  "https://storageAccountName.blob.core.windows.net",
  credential
);

在 Express.js 应用程序中,可以将凭据存储在应用设置中,并在路由处理程序中访问凭据:

import express from "express";
import { DefaultAzureCredential, ManagedIdentityCredential } from "@azure/identity";
import { SecretClient } from "@azure/keyvault-secrets";
import { BlobServiceClient } from "@azure/storage-blob";

const app = express();

// Create a single credential instance at app startup
app.locals.credential = process.env.NODE_ENV === 'production'
  ? new ManagedIdentityCredential(process.env.AZURE_CLIENT_ID)
  : new DefaultAzureCredential();

// Reuse the credential in route handlers
app.get('/api/secrets/:secretName', async (req, res) => {
  const secretClient = new SecretClient(
    "https://keyVaultName.vault.azure.net", 
    req.app.locals.credential
  );
  
  try {
    const secret = await secretClient.getSecret(req.params.secretName);
    res.json({ name: secret.name, value: secret.value });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// Add this route to the existing Express app
app.get('/api/blobs/:containerName', async (req, res) => {
  const blobServiceClient = new BlobServiceClient(
    "https://storageAccountName.blob.core.windows.net", 
    req.app.locals.credential
  );
  
  try {
    // Get reference to a container
    const containerClient = blobServiceClient.getContainerClient(req.params.containerName);
    
    // List all blobs in the container
    const blobs = [];
    for await (const blob of containerClient.listBlobsFlat()) {
      blobs.push({
        name: blob.name,
        contentType: blob.properties.contentType,
        size: blob.properties.contentLength,
        lastModified: blob.properties.lastModified
      });
    }
    
    res.json({ containerName: req.params.containerName, blobs });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

app.listen(3000, () => console.log('Server running on port 3000'));

了解何时需要令牌生存期和缓存逻辑

如果在 Azure SDK 客户端库的上下文之外使用 Azure 标识库凭据,那么就需要您来负责管理应用中的令牌生存期和缓存行为。

refreshAfterTimestamp AccessToken 上的属性为使用者提供何时可以尝试刷新令牌的提示,这一属性将被依赖于 Azure Core 库的 Azure SDK 客户端库自动用来刷新令牌。 为了直接使用支持令牌缓存的 Azure 身份库凭据,当 refreshAfterTimestamp 时间发生时,底层 MSAL 缓存会自动刷新。 此设计允许客户端代码每次需要令牌时调用 TokenCredential.getToken(),并将令牌刷新过程委托给库。

若要仅在必要时调用 TokenCredential.getToken(),请观察 refreshAfterTimestamp 日期,并主动尝试在该时间之后刷新令牌。 具体实现由客户决定。

了解托管标识重试策略

使用适用于 JavaScript 的 Azure 标识库,可以使用托管标识进行身份验证ManagedIdentityCredential。 使用 ManagedIdentityCredential 的方式会影响应用的重试策略:

  • 使用DefaultAzureCredential时,如果初始令牌获取尝试失败或在短时间内超时,则不会进行重试。 这是复原能力最低的选项,因为它被优化为“快速失败”,以实现高效的开发内部循环。
  • 任何其他方法,例如直接使用 ChainedTokenCredentialManagedIdentityCredential
    • 默认情况下,重试的时间间隔至少为 0.8 秒,最多可重试 5 次。 此选项针对复原能力进行了优化,但在开发内部循环中引入了可能不需要的延迟。
    • 若要更改任何默认重试设置,请在 options 参数上使用 retryOptions 属性。 例如,最多重试三次,起始间隔为 0.5 秒:
import { ManagedIdentityCredential } from "@azure/identity";

const credential = new ManagedIdentityCredential(
  process.env.AZURE_CLIENT_ID, // For user-assigned managed identity
  {
    retryOptions: {
      maxRetries: 3,           // Maximum number of retry attempts
      retryDelayInMs: 500,     // Initial delay between retries (in milliseconds)
      maxRetryDelayInMs: 5000  // Maximum delay between retries (in milliseconds)
    }
  }
);

有关自定义托管标识重试策略的更多信息,请查看以下从 TokenCredentialOptions 扩展而来的选项之一: