Microsoft.Identity.Web 支持基于证书的身份验证,作为机密客户端应用程序的安全替代方法。 证书使用非对称加密,因此只有私钥持有者才能进行身份验证。
在本文中,将配置来自各种源的证书凭据,将其注册到应用,并在生产环境中对其进行管理。
为何使用证书?
| 因子 | 客户端密码 | 证书 |
|---|---|---|
| 安全性 | 共享机密(对称) | 非对称密钥对 |
| 旋转 | 要求应用重新部署或配置更改 | 可以通过密钥保管库实现自动化 |
| 暴露风险 | 配置中的机密可能会泄露 | 私钥保留在安全存储中 |
| 遵从性 | 可能不符合企业策略 | 满足大多数企业安全要求 |
| 推荐用于 | 开发、原型制作 | 生产工作负荷 |
重要
Microsoft建议对生产应用程序的客户端机密使用证书。 为获得最高安全状况,请在托管环境支持时使用 无证书身份验证 (托管标识或工作负荷联合身份验证)。
工作原理
- 使用私钥生成或获取 X.509 证书。
- 使用 Microsoft Entra 应用注册证书的 公钥(或指纹)。
- 在运行时,Microsoft。Identity.Web 从配置的源加载证书(包括私钥)。
- 该库使用私钥对客户端断言进行签名,然后将该断言发送到 Microsoft Entra ID,以获取令牌。
证书源
Microsoft。Identity.Web 支持从多个源加载证书:
| 源类型 |
SourceType 值 |
最佳用途 |
|---|---|---|
| Azure 密钥保管库 | KeyVault |
生产(推荐) |
| 证书存储 |
StoreWithThumbprint 或 StoreWithDistinguishedName |
本地Windows服务器 |
| 文件路径 | Path |
开发、容器化应用 |
| Base64 编码的字符串 | Base64Encoded |
Kubernetes 密钥、CI/CD 管道 |
在ClientCertificates(或AzureAd)配置部分的AzureAdB2C数组中配置证书凭据。 可以为轮换方案指定多个证书 - Microsoft。Identity.Web 使用它找到的第一个有效证书。
从 Azure 密钥保管库(推荐使用)
Azure 密钥保管库是生产中证书的建议源。 它提供集中式管理、访问控制、审核和自动轮换功能。
配置
将证书配置添加到您的appsettings.json中。
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "your-tenant-id",
"ClientId": "your-client-id",
"ClientCertificates": [
{
"SourceType": "KeyVault",
"KeyVaultUrl": "https://your-keyvault-name.vault.azure.net",
"KeyVaultCertificateName": "your-certificate-name"
}
]
}
}
| 财产 | 说明 |
|---|---|
SourceType |
必须是 "KeyVault"。 |
KeyVaultUrl |
Azure 密钥保管库的 URI(例如,https://myapp-kv.vault.azure.net)。 |
KeyVaultCertificateName |
存储在密钥保管库中的证书的名称。 |
设置 密钥保管库 访问策略
应用程序的标识必须有权从密钥保管库(密钥保管库)读取证书。 授予此权限的方式取决于是使用保管库访问策略模型还是Azure基于角色的访问控制(RBAC)。
选项 1:保管库访问策略
az keyvault set-policy \
--name your-keyvault-name \
--object-id <app-or-managed-identity-object-id> \
--certificate-permissions get list \
--secret-permissions get
注释
--secret-permissions get 权限是必需的,因为Azure 密钥保管库将私钥存储为链接到证书的机密。 Microsoft。Identity.Web 需要访问证书及其私钥。
选项 2:Azure RBAC
将 密钥保管库 证书用户 角色分配给应用程序标识:
az role assignment create \
--role "Key Vault Certificate User" \
--assignee <app-or-managed-identity-object-id> \
--scope /subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.KeyVault/vaults/<vault-name>
使用托管标识访问密钥保管库
当你的应用程序在 Azure(应用服务、Azure Functions、Azure Kubernetes 服务、虚拟机)中运行时,请使用托管标识来验证访问 密钥保管库。 这样就无需任何凭据即可访问保管库本身。
系统分配的托管标识
如果应用启用了系统分配的托管标识,Microsoft。Identity.Web 自动使用 DefaultAzureCredential 对密钥保管库进行身份验证。 除了ClientCertificates条目之外,不需要额外的配置。
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "your-tenant-id",
"ClientId": "your-client-id",
"ClientCertificates": [
{
"SourceType": "KeyVault",
"KeyVaultUrl": "https://your-keyvault-name.vault.azure.net",
"KeyVaultCertificateName": "your-certificate-name"
}
]
}
}
用户分配的托管标识
对于用户分配的托管标识,请在密钥保管库证书描述符上指定 ManagedIdentityClientId:
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "your-tenant-id",
"ClientId": "your-client-id",
"ClientCertificates": [
{
"SourceType": "KeyVault",
"KeyVaultUrl": "https://your-keyvault-name.vault.azure.net",
"KeyVaultCertificateName": "your-certificate-name",
"ManagedIdentityClientId": "user-assigned-managed-identity-client-id"
}
]
}
}
小窍门
在开发期间本地运行时,DefaultAzureCredential 会回退到你的 Azure CLI 或 Visual Studio 凭据。 请确保使用 az login 登录,并且开发人员帐户具有适当的密钥保管库权限。
从证书存储区(仅限于Windows)
在Windows,可以从Windows证书存储加载证书。 这在本地或 IIS 托管的部署中很常见。
通过拇指指纹
使用 StoreWithThumbprint 通过其 SHA-1 指纹标识证书:
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "your-tenant-id",
"ClientId": "your-client-id",
"ClientCertificates": [
{
"SourceType": "StoreWithThumbprint",
"CertificateStorePath": "CurrentUser/My",
"CertificateThumbprint": "A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2"
}
]
}
}
| 财产 | 说明 |
|---|---|
SourceType |
必须是 "StoreWithThumbprint"。 |
CertificateStorePath |
证书存储位置。 常见值: "CurrentUser/My", "LocalMachine/My"。 |
CertificateThumbprint |
证书的 SHA-1 指纹(40 个十六进制字符)。 |
按专有名称
使用 StoreWithDistinguishedName 按其主题名称标识证书:
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "your-tenant-id",
"ClientId": "your-client-id",
"ClientCertificates": [
{
"SourceType": "StoreWithDistinguishedName",
"CertificateStorePath": "CurrentUser/My",
"CertificateDistinguishedName": "CN=MyAppCertificate"
}
]
}
}
| 财产 | 说明 |
|---|---|
SourceType |
必须是 "StoreWithDistinguishedName"。 |
CertificateStorePath |
证书存储位置。 常见值: "CurrentUser/My", "LocalMachine/My"。 |
CertificateDistinguishedName |
证书的使用者可分辨名称(例如, "CN=MyAppCertificate")。 |
证书存储位置
下表列出了常见的证书存储路径以及访问它们所需的权限:
| 路径 | 说明 | 所需的权限 |
|---|---|---|
CurrentUser/My |
当前用户的个人存储 | 用户级访问权限 |
LocalMachine/My |
计算机范围的个人存储 | 管理员访问权限 |
LocalMachine/Root |
受信任的根证书颁发机构 | 管理员访问权限 |
CurrentUser/Root |
当前用户受信任的根证书颁发机构 | 用户级访问权限 |
注释
在 IIS 中托管时,应用程序池标识必须具有对证书私钥的读取访问权限。 可以使用证书 MMC 管理单元中的 “管理私钥 ”选项授予此权限。
从文件路径
可以直接从 .pfx 磁盘上的 (PKCS#12) 文件加载证书。
警告
不建议在生产环境中,将带有密码的证书文件存储在磁盘上。 仅对本地开发或文件系统受到保护的环境(例如,在容器中装载的机密)中使用此方法。
配置
将证书文件路径和密码添加到appsettings.json中。
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "your-tenant-id",
"ClientId": "your-client-id",
"ClientCertificates": [
{
"SourceType": "Path",
"CertificateDiskPath": "/path/to/certificate.pfx",
"CertificatePassword": "your-certificate-password"
}
]
}
}
| 财产 | 说明 |
|---|---|
SourceType |
必须是 "Path"。 |
CertificateDiskPath |
.pfx 文件的绝对路径或相对路径。 |
CertificatePassword |
.pfx文件的密码。 如果证书没有密码,请省略此属性或将其设置为空字符串。 |
小窍门
若要避免以纯文本 appsettings.json形式存储密码,请从环境变量或机密管理器中引用密码:
使用.NET用户机密(开发):
dotnet user-secrets set "AzureAd:ClientCertificates:0:CertificatePassword" "your-password"
使用环境变量:
export AzureAd__ClientCertificates__0__CertificatePassword="your-password"
从 Base64 编码的值
可以将证书作为 Base64 编码的字符串提供。 通过环境变量、Kubernetes 机密或 CI/CD 管道变量注入证书时,此方法非常有用。
配置
将 Base64 编码的证书值添加到您的 appsettings.json 中:
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "your-tenant-id",
"ClientId": "your-client-id",
"ClientCertificates": [
{
"SourceType": "Base64Encoded",
"Base64EncodedValue": "MIIKcQIBAzCCCi0GCSqGSIb3DQEHAaCCCh4Egg..."
}
]
}
}
| 财产 | 说明 |
|---|---|
SourceType |
必须是 "Base64Encoded"。 |
Base64EncodedValue |
编码为 Base64 字符串的完整证书(包括私钥)。 |
生成 Base64 值
将 .pfx 文件转换为 Base64 字符串:
PowerShell:
$certBytes = [System.IO.File]::ReadAllBytes("path/to/certificate.pfx")
$base64 = [System.Convert]::ToBase64String($certBytes)
$base64 | Set-Clipboard # Copies to clipboard
Bash:
base64 -w 0 path/to/certificate.pfx
与 Kubernetes 机密配合使用
将 Base64 编码的证书存储在 Kubernetes 机密中,并将其映射到环境变量:
apiVersion: v1
kind: Secret
metadata:
name: app-cert-secret
type: Opaque
data:
AzureAd__ClientCertificates__0__Base64EncodedValue: <base64-encoded-pfx>
在部署中引用该密钥:
env:
- name: AzureAd__ClientCertificates__0__SourceType
value: "Base64Encoded"
- name: AzureAd__ClientCertificates__0__Base64EncodedValue
valueFrom:
secretKeyRef:
name: app-cert-secret
key: AzureAd__ClientCertificates__0__Base64EncodedValue
在 CI/CD 管道中使用
在Azure DevOps或GitHub Actions中,将 Base64 编码的证书存储为机密变量,然后在运行时将其设置为环境变量。
GitHub Actions example:
env:
AzureAd__ClientCertificates__0__SourceType: "Base64Encoded"
AzureAd__ClientCertificates__0__Base64EncodedValue: ${{ secrets.APP_CERTIFICATE_BASE64 }}
Azure DevOps example:
variables:
AzureAd__ClientCertificates__0__SourceType: "Base64Encoded"
AzureAd__ClientCertificates__0__Base64EncodedValue: $(AppCertificateBase64)
重要
尽管证书是 Base64 编码的,但它包含私钥,并且必须被视为机密。 始终在 CI/CD 管道中使用机密变量 — 从不将 Base64 编码的证书提交到源代码管理。
在 C# 代码中配置证书
除了 JSON 配置,还可以使用 CredentialDescription 中的 Microsoft.Identity.Abstractions 类以编程方式配置证书凭据。
辅助方法
该 CredentialDescription 类为每个证书源类型提供静态帮助程序方法:
using Microsoft.Identity.Abstractions;
// From Azure Key Vault
var kvCredential = CredentialDescription.FromKeyVault(
"https://your-keyvault-name.vault.azure.net",
"your-certificate-name");
// From certificate store (by thumbprint)
var thumbprintCredential = CredentialDescription.FromCertificateStore(
"CurrentUser/My",
thumbprint: "A1B2C3D4E5F6A1B2C3D4E5F6A1B2C3D4E5F6A1B2");
// From certificate store (by distinguished name)
var dnCredential = CredentialDescription.FromCertificateStore(
"CurrentUser/My",
distinguishedName: "CN=MyAppCertificate");
// From file path
var pathCredential = CredentialDescription.FromCertificatePath(
"/path/to/certificate.pfx",
"your-certificate-password");
// From Base64-encoded string
var base64Credential = CredentialDescription.FromBase64String(
"MIIKcQIBAzCCCi0GCSqGSIb3DQEHAaCCCh4Egg...");
在 ASP.NET Core中使用
配置身份验证时,直接传递凭据说明:
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(options =>
{
options.Instance = "https://login.microsoftonline.com/";
options.TenantId = "your-tenant-id";
options.ClientId = "your-client-id";
options.ClientCredentials = new[]
{
CredentialDescription.FromKeyVault(
"https://your-keyvault-name.vault.azure.net",
"your-certificate-name")
};
});
小窍门
帮助程序方法等效于手动设置对象的属性 CredentialDescription 。 在代码中配置凭据而不是通过 appsettings.json时,它们提供更简洁的语法。
创建用于开发的自签名证书
对于本地开发和测试,可以创建自签名证书。 请勿在生产环境中使用自签名证书。
使用 PowerShell (Windows)
运行以下命令以创建自签名证书、导出证书并显示指纹:
$cert = New-SelfSignedCertificate `
-Subject "CN=MyDevCertificate" `
-CertStoreLocation "Cert:\CurrentUser\My" `
-KeyExportPolicy Exportable `
-KeySpec Signature `
-KeyLength 2048 `
-KeyAlgorithm RSA `
-HashAlgorithm SHA256 `
-NotAfter (Get-Date).AddYears(2)
# Export the .pfx file (with private key)
$password = ConvertTo-SecureString -String "YourPassword123!" -Force -AsPlainText
Export-PfxCertificate -Cert $cert -FilePath ".\MyDevCertificate.pfx" -Password $password
# Export the .cer file (public key only — for app registration)
Export-Certificate -Cert $cert -FilePath ".\MyDevCertificate.cer"
# Display the thumbprint
Write-Host "Thumbprint: $($cert.Thumbprint)"
使用 OpenSSL (跨平台)
运行以下命令以生成证书,将其打包为 .pfx 文件,并显示指纹:
# Generate a self-signed certificate and private key
openssl req -x509 -newkey rsa:2048 \
-keyout key.pem -out cert.pem \
-days 730 -nodes \
-subj "/CN=MyDevCertificate"
# Package into a .pfx file
openssl pkcs12 -export \
-out MyDevCertificate.pfx \
-inkey key.pem -in cert.pem \
-passout pass:YourPassword123!
# Get the thumbprint
openssl x509 -in cert.pem -noout -fingerprint -sha1
使用 .NET CLI
将开发 HTTPS 证书导出为 .pfx 文件:
dotnet dev-certs https --export-path ./MyDevCertificate.pfx --password YourPassword123!
注释
该 dotnet dev-certs 命令生成 HTTPS 开发证书。 虽然它可用于测试证书加载,但它主要用于本地 HTTPS,可能不适合所有身份验证测试方案。
在 Microsoft Entra ID 中注册证书
创建或获取证书后,必须在Microsoft Entra ID中向应用注册注册其公钥。
使用 Azure 门户
- 转到 Azure 门户并导航到 Microsoft Entra ID>应用注册。
- 选择自己的应用程序。
- 选择“证书和机密”“证书”>“上传证书”>。
- 上传包含
.cer的.pem或文件。 不要上传.pfx包含私钥的文件。 - 请注意上传后显示的 指纹 值 - 可能需要它进行配置。
使用Azure CLI
az ad app credential reset \
--id <application-client-id> \
--cert @/path/to/certificate.pem \
--append
该 --append 标志添加证书而不删除现有凭据。
使用 Microsoft Graph PowerShell
$certData = [System.IO.File]::ReadAllBytes(".\MyDevCertificate.cer")
$base64Cert = [System.Convert]::ToBase64String($certData)
$keyCredential = @{
type = "AsymmetricX509Cert"
usage = "Verify"
key = [System.Convert]::FromBase64String($base64Cert)
displayName = "MyAppCertificate"
}
Update-MgApplication -ApplicationId <app-object-id> -KeyCredentials @($keyCredential)
重要
仅将 公钥 (.cer 或 .pem)上传到应用注册。 从不上传 .pfx 包含私钥的文件。 私钥必须保持安全存储和仅可供应用程序访问。
证书轮换
证书轮换在证书过期之前将其替换为新证书,以确保服务连续性。
策略:重叠证书
建议的方法使用重叠的有效期:
- 在当前证书过期之前生成新证书(例如提前 30-60 天)。
- 在 Microsoft Entra 应用注册中同时注册新证书和现有证书。 Microsoft Entra ID接受任何已注册证书签名的令牌。
- 将新证书部署到应用程序的证书源(密钥保管库、证书存储等)。
- 更新配置 (如有必要),以指向新证书。
- 确认所有实例都使用新实例后,从应用注册中删除旧证书。
配置中的多个证书
Microsoft。Identity.Web 支持指定多个证书。 库函数按顺序尝试这些证书,并使用第一个有效的证书:
{
"AzureAd": {
"ClientCertificates": [
{
"SourceType": "KeyVault",
"KeyVaultUrl": "https://your-keyvault.vault.azure.net",
"KeyVaultCertificateName": "new-cert-2026"
},
{
"SourceType": "KeyVault",
"KeyVaultUrl": "https://your-keyvault.vault.azure.net",
"KeyVaultCertificateName": "current-cert-2025"
}
]
}
}
使用 Azure 密钥保管库 自动旋转
Azure 密钥保管库支持自动证书续订。 启用自动旋转时:
- 密钥保管库在过期之前生成新的证书版本。
- Microsoft Identity.Web 自动获取最新版本(在下一个证书获取时)。
- 旧证书版本在过期之前保持有效。
若要在密钥保管库中配置自动轮换,请执行:
az keyvault certificate set-attributes \
--vault-name your-keyvault-name \
--name your-certificate-name \
--policy @rotation-policy.json
小窍门
对于具有长时间运行进程的应用程序,请考虑实现定期证书刷新。 Microsoft。Identity.Web 将证书缓存在内存中。 如果证书在密钥保管库中轮换,则下次需要创建新的 MSAL 机密客户端应用程序实例时,应用程序会选取新证书。
排查证书错误
本部分列出了常见的错误消息及其解决方案。
常见错误
找不到证书
错误消息:
System.Security.Cryptography.CryptographicException: The certificate cannot be found.
可能的原因和解决方案:
| 原因 | 解决方案 |
|---|---|
| 指纹不正确 | 验证配置中的指纹是否与已安装的证书匹配。 删除任何隐藏字符(空格、不可见 Unicode)。 |
| 证书存储错误 | 确认 CertificateStorePath 与证书安装位置(CurrentUser/My 与 LocalMachine/My)的匹配。 |
| 未安装证书 | 使用 certmgr.msc (CurrentUser) 或 certlm.msc (LocalMachine) 将证书导入到正确的存储中。 |
| 密钥保管库名称不匹配 | 验证 KeyVaultUrl 并 KeyVaultCertificateName 正确。 |
| 找不到文件 | 确认 CertificateDiskPath 指向现有 .pfx 文件和应用程序具有读取访问权限。 |
拒绝访问密钥保管库
错误消息:
Azure.RequestFailedException: The user, group or application '...' does not have certificates get permission on key vault '...'
解决方案:
- 验证访问策略是否同时授予
get证书和机密的权限。 - 如果使用 Azure RBAC,请确保身份具有 密钥保管库 证书用户角色。
- 对于托管身份,请确认身份已启用,并在策略中使用正确的对象 ID。
证书私钥不可访问
错误消息:
System.Security.Cryptography.CryptographicException: Keyset does not exist.
解决方案:
- 在 Windows/IIS 上,确保应用程序池标识具有对私钥read访问权限。 使用 MMC 证书管理单元,通过 “管理私钥”授予访问权限。
- 在 Linux 上,验证
.pfx该文件是否具有适当的文件权限(chmod 600)。 - 确保使用私钥(
Export-PfxCertificate或openssl pkcs12 -export)导出证书。
证书已过期
错误消息:
AADSTS700027: Client assertion contains an invalid signature. The key was expired.
解决方案:
- 检查证书的有效期:
openssl x509 -in cert.pem -noout -dates。 - 生成新的证书并更新应用注册和应用程序的配置。
- 实现证书轮换以防止将来的过期问题。 请参阅 证书轮换。
证书密码错误
错误消息:
System.Security.Cryptography.CryptographicException: The specified network password is not correct.
解决方案:
- 验证
CertificatePassword是否与导出.pfx文件时使用的密码匹配。 - 如果使用环境变量,请检查编码问题(尾随换行符、特殊字符)。
- 使用已知密码重新导出证书。
诊断清单
当证书身份验证不起作用时,请使用此清单:
- [ ] 证书有效性 — 证书是否在其有效期内? 检查
NotBefore和NotAfter日期。 - [ ] 应用注册 — 证书的公钥是否已上传到正确的应用注册?
- [ ] 指纹匹配 — 配置中的指纹是否与应用注册中的证书匹配?
- [ ] 私钥访问 — 应用程序是否可以读取证书的私钥?
- [ ] 密钥保管库权限 — 在密钥保管库源中,该标识是否拥有
certificates/get权限和secrets/get权限? - [ ] 配置节 — 证书配置是否位于正确的节(
AzureAd或AzureAdB2C)下? - [ ] NuGet 包 —
Microsoft.Identity.Web是最新的吗? 旧版本可能缺乏对某些证书源类型的支持。
启用日志记录
若要获取详细的诊断信息,请启用 MSAL 日志记录:
builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration, "AzureAd")
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches();
builder.Logging.AddFilter("Microsoft.Identity", LogLevel.Debug);
查看有关证书加载、客户端断言创建和令牌获取的消息的日志。