在 Node.js Web 应用中使用客户端证书进行身份验证
适用于: 员工租户 外部租户(了解详细信息)
Microsoft Entra External ID 支持两种类型的机密客户端应用程序身份验证:基于密码的身份验证(例如客户端机密)和基于证书的身份验证。 为了提高安全级别,建议在机密客户端应用程序中使用证书(而非客户端机密)作为凭据。
在生产中,你应购买由知名证书颁发机构签署的证书,然后使用 Azure Key Vault 管理证书访问和生命周期。 但是,出于测试目的,你可以创建一个自签名证书并将应用配置为使用它进行身份验证。
本文介绍如何在 Azure 门户、OpenSSL 或 PowerShell 上使用 Azure Key Vault 生成自签名证书。 如果你已有客户端密码,你将了解如何安全地删除它。
还可以根据需要使用 .NET、Node.js、Go、Python 或 Java 客户端库以编程方式创建自签名证书。
先决条件
Visual Studio Code 或其他代码编辑器。
外部租户。 如果还没有,请注册免费试用版。
OpenSSL,也可通过 Chocolatey 在 Windows 中轻松安装 OpenSSL。
Windows PowerShell 或 Azure 订阅。
创建自签名证书
如果本地计算机中已有自签名证书,则可跳过此步骤,然后将证书上传到应用注册。
可以使用 Azure Key Vault 为应用生成自签名证书。 通过使用 Azure Key Vault,你将享有多项权益,例如分配合作伙伴证书颁发机构 (CA) 和自动执行证书轮换。
如果你在 Azure Key Vault 中已有自签名证书,想在不下载的情况下使用它,请跳过此步骤,然后使用直接来自 Azure Key Vault 的自签名证书。 否则,请使用以下步骤生成证书
按照使用 Azure 门户在 Azure Key Vault 中设置和检索证书中的步骤创建和下载证书。
创建证书后,下载 .cer 文件和 .pfx 文件,例如 ciam-client-app-cert.cer 和 ciam-client-app-cert.pfx。 .cer 文件包含公钥,是上传到 Microsoft Entra 管理中心的内容。
在终端中运行以下命令,从 .pfx 文件中提取私钥。 当系统提示键入通行短语时,如果不想设置它,只需按 Enter 键即可。 否则,请键入所选通行短语:
openssl pkcs12 -in ciam-client-app-cert.pfx -nocerts -out ciam-client-app-cert.key
ciam-client-app-cert.key 文件是你在应用中使用的文件。
将证书上传到应用注册
若要使用客户端应用证书,你需要将自己在 Microsoft Entra 管理中心注册的应用与证书相关联:
至少以应用程序管理员的身份登录到 Microsoft Entra 管理中心。
如果你有权访问多个租户,请使用顶部菜单中的“设置”图标 ,通过“目录 + 订阅”菜单切换到你的外部租户。
浏览到“标识”>“应用程序”>“应用注册”。
从应用注册列表中,选择要与证书关联的应用,例如 ciam-client-app。
在“管理”下,选择“证书和机密”。
选择“证书”,然后选择“上传证书”。
选择“选择文件”文件图标,然后选择要上传的证书,例如 ciam-client-app-cert.pem、ciam-client-app-cert.cer 或 ciam-client-app-cert.crt。
对于“说明”,请键入说明(例如“CIAM 客户端应用证书”),然后选择“添加”以上传证书。 上传证书后,会显示“指纹”、“开始日期”和“到期日期”值。
记录“指纹”值,供稍后在配置客户端应用时使用。
如果你已为应用程序准备好客户端密码,则需要将其删除,以避免恶意应用程序冒充你的应用程序:
- 转到“客户端机密”选项卡,然后选择“删除”图标。
- 在显示的弹出消息中,选择“是”。
将 Node.js 应用配置为使用证书
将应用注册与证书关联后,需要更新应用代码才能开始使用证书:
找到包含 MSAL 配置对象的文件(例如 authConfig.js 中的
msalConfig
),然后更新它,使其类似于以下代码。 如果你有客户端密码,请确保将其删除:require('dotenv').config(); const fs = require('fs'); //// import the fs module for reading the key file const crypto = require('crypto'); const TENANT_SUBDOMAIN = process.env.TENANT_SUBDOMAIN || 'Enter_the_Tenant_Subdomain_Here'; const REDIRECT_URI = process.env.REDIRECT_URI || 'http://localhost:3000/auth/redirect'; const POST_LOGOUT_REDIRECT_URI = process.env.POST_LOGOUT_REDIRECT_URI || 'http://localhost:3000'; const privateKeySource = fs.readFileSync('PATH_TO_YOUR_PRIVATE_KEY_FILE') const privateKeyObject = crypto.createPrivateKey({ key: privateKeySource, passphrase: 'Add_Passphrase_Here', format: 'pem' }); const privateKey = privateKeyObject.export({ format: 'pem', type: 'pkcs8' }); /** * Configuration object to be passed to MSAL instance on creation. * For a full list of MSAL Node configuration parameters, visit: * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/docs/configuration.md */ const msalConfig = { auth: { clientId: process.env.CLIENT_ID || 'Enter_the_Application_Id_Here', // 'Application (client) ID' of app registration in Azure portal - this value is a GUID authority: process.env.AUTHORITY || `https://${TENANT_SUBDOMAIN}.ciamlogin.com/`, clientCertificate: { thumbprint: "YOUR_CERT_THUMBPRINT", // replace with thumbprint obtained during step 2 above privateKey: privateKey } }, //... Rest of code in the msalConfig object }; module.exports = { msalConfig, REDIRECT_URI, POST_LOGOUT_REDIRECT_URI, TENANT_SUBDOMAIN };
在代码中替换占位符:
将
Add_Passphrase_Here
替换为用于加密私钥的通行短语。将
YOUR_CERT_THUMBPRINT
替换为前面记录的“指纹”值。将
PATH_TO_YOUR_PRIVATE_KEY_FILE
替换为私钥文件的文件路径。将
Enter_the_Application_Id_Here
替换为之前注册的应用的应用程序(客户端)ID。查找
Enter_the_Tenant_Subdomain_Here
并将其替换为目录(租户)子域。 例如,如果租户主域为contoso.onmicrosoft.com
,请使用contoso
。 如果没有租户名称,请了解如何读取租户详细信息。
我们加密了此密钥(建议你也这样做),因此必须在将其传递给 MSAL 配置对象之前将其解密。
//... const privateKeyObject = crypto.createPrivateKey({ key: privateKeySource, passphrase: 'Add_Passphrase_Here', format: 'pem' }); const privateKey = privateKeyObject.export({ format: 'pem', type: 'pkcs8' }); //...
使用运行并测试 Web 应用中的步骤来测试应用。
直接从 Azure Key Vault 使用自签名证书
可以直接从 Azure Key Vault 使用现有证书:
找到包含 MSAL 配置对象的文件(例如 authConfig.js 中的
msalConfig
),然后删除clientSecret
属性:const msalConfig = { auth: { clientId: process.env.CLIENT_ID || 'Enter_the_Application_Id_Here', // 'Application (client) ID' of app registration in Azure portal - this value is a GUID authority: process.env.AUTHORITY || `https://${TENANT_SUBDOMAIN}.ciamlogin.com/`, }, //... };
安装 Azure CLI,然后在控制台上键入以下命令进行登录:
az login --tenant YOUR_TENANT_ID
将占位符
YOUR_TENANT_ID
替换为之前复制的目录(租户)ID。在控制台上,键入以下命令来安装所需的包:
npm install --save @azure/identity @azure/keyvault-certificates @azure/keyvault-secrets
在客户端应用中,使用以下代码生成
thumbprint
和privateKey
;const identity = require("@azure/identity"); const keyvaultCert = require("@azure/keyvault-certificates"); const keyvaultSecret = require('@azure/keyvault-secrets'); const KV_URL = process.env["KEY_VAULT_URL"] || "ENTER_YOUR_KEY_VAULT_URL" const CERTIFICATE_NAME = process.env["CERTIFICATE_NAME"] || "ENTER_THE_NAME_OF_YOUR_CERTIFICATE_ON_KEY_VAULT"; // Initialize Azure SDKs const credential = new identity.DefaultAzureCredential(); const certClient = new keyvaultCert.CertificateClient(KV_URL, credential); const secretClient = new keyvaultSecret.SecretClient(KV_URL, credential); async function getKeyAndThumbprint() { // Grab the certificate thumbprint const certResponse = await certClient.getCertificate(CERTIFICATE_NAME).catch(err => console.log(err)); const thumbprint = certResponse.properties.x509Thumbprint.toString('hex') // When you upload a certificate to Key Vault, a secret containing your private key is automatically created const secretResponse = await secretClient.getSecret(CERTIFICATE_NAME).catch(err => console.log(err));; // secretResponse contains both public and private key, but we only need the private key const privateKey = secretResponse.value.split('-----BEGIN CERTIFICATE-----\n')[0] } getKeyAndThumbprint();
在代码中替换占位符:
将
ENTER_YOUR_KEY_VAULT_URL
替换为你的 Azure Key Vault URL。将
ENTER_THE_NAME_OF_YOUR_CERTIFICATE_ON_KEY_VAULT
替换为你在 Azure Key Vault 中的证书的名称。
使用
thumbprint
和privateKey
值更新配置:let clientCert = { thumbprint: thumbprint, privateKey: privateKey, }; msalConfig.auth.clientCertificate = clientCert; //For this to work, you can't declares your msalConfig using const modifier
然后实例化机密客户端,如
getMsalInstance
方法所示:class AuthProvider { //... getMsalInstance(msalConfig) { return new msal.ConfidentialClientApplication(msalConfig); } //... }
使用运行并测试 Web 应用中的步骤来测试应用。
后续步骤
了解如何: