如何使用 API 管理中的客户端证书身份验证确保 API 安全

API 管理提供的功能可确保使用客户端证书和相互 TLS 身份验证安全地访问 API(即,客户端到 API 管理)。 你可以验证连接客户端提供的证书,并使用策略表达式,对照所需的值检查证书属性。

若要了解如何使用客户端证书保护对 API 后端服务的访问(即,从 API 管理到后端),请参阅如何使用客户端证书身份验证保护后端服务

有关 API 授权的概念概述,请参阅 API 管理中对 API 的身份验证和授权

证书选项

对于证书验证,API 管理可以根据 API 管理实例中管理的证书进行检查。 如果选择使用 API 管理来管理客户端证书,则可以使用以下选项:

  • 引用 Azure Key Vault 中托管的证书
  • 直接在 API 管理中添加证书文件

建议使用密钥保管库证书,因为它有助于提高 API 管理安全性:

  • 密钥保管库中存储的证书可以在服务上重复使用
  • 粒度访问策略 可应用到密钥保管库中存储的证书
  • 密钥保管库中更新的证书会在 API 管理中自动轮换。 在密钥保管库中更新后,API 管理中的证书会在 4 小时内更新。 你还可以使用 Azure 门户或通过管理 REST API 手动刷新证书。

先决条件

  • 如果尚未创建 API 管理服务实例,请参阅创建 API 管理服务实例

  • 需要有权访问证书以及 Azure 密钥保管库中用于管理的密码,或上传到 API 管理服务。 证书必须采用 CER 或 PFX 格式。 允许使用自签名证书。

    如果使用自签名证书,还应在 API 管理实例中安装受信任的根和中间 CA 证书

    注意

    “消耗”层不支持用于证书验证的 CA 证书。

密钥保管库集成的先决条件

  1. 如果还没有密钥保管库,请创建一个。 有关创建密钥保管库的步骤,请参阅快速入门:使用 Azure 门户创建密钥保管库

    若要创建证书或将证书导入密钥保管库,请参阅快速入门:使用 Azure 门户从 Azure 密钥保管库设置和检索证书

  2. 在 API 管理实例中启用系统分配的或用户分配的托管标识

配置对密钥保管库的访问权限

  1. 在门户中导航到你的密钥保管库。

  2. 在左侧菜单中,选择“访问配置”,并记下配置的权限模型。

  3. 根据权限模型,为 API 管理托管标识配置密钥保管库访问策略Azure RBAC 访问

    添加密钥保管库访问策略:

    1. 在左侧菜单中,选择“访问策略”。
    2. 在“访问策略”页上,选择“+ 创建”。
    3. 在“权限”选项卡上的“机密权限”下,选中“获取”和“列出”,然后选择“下一步”。
    4. 在“主体”选项卡上的“选择主体”中,搜索托管标识的资源名称,然后选择“下一步”。 如果你使用系统分配的标识,则主体为你的 API 管理实例的名称。
    5. 再次选择“下一步”。 在“查看 + 创建”选项卡上,选择“创建”。

    配置 Azure RBAC 访问:

    1. 在左侧菜单中,选择“访问控制(IAM)”。
    2. 在“访问控制(IAM)”页上,选择“添加角色分配”。
    3. 在“角色”选项卡上,选择“密钥保管库机密用户”。
    4. 在“成员”选项卡上,选择“托管标识”>“选择成员”。
    5. 在“选择托管标识”页上,选择系统分配的托管标识或与 API 管理实例关联的用户分配的托管标识,然后选择“选择”。
    6. 选择“查看 + 分配”。

Key Vault 防火墙要求

如果在密钥保管库上启用了 Key Vault 防火墙,则具有以下附加要求:

  • 必须使用 API 管理实例的“系统分配的”托管标识来访问密钥保管库。

  • 在 Key Vault 防火墙中,启用“允许受信任的 Microsoft 服务绕过此防火墙”选项。

  • 在选择要添加到 Azure API 管理的证书或机密时,请确保允许本地客户端 IP 地址临时访问密钥保管库。 有关详细信息,请参阅配置 Azure Key Vault 网络设置

    完成配置后,可以在密钥保管库防火墙中阻止客户端地址。

虚拟网络要求

如果 API 管理实例部署在虚拟网络中,则还应配置下列网络设置:

  • 在 API 管理子网上,启用 Azure Key Vault 的服务终结点
  • 配置一个网络安全组 (NSG) 规则,以允许指向 AzureKeyVault 和 AzureActiveDirectory 服务标记的出站流量。

有关详细信息,请参阅在 VNET 中设置 Azure API 管理时使用的网络配置

添加密钥保管库证书

请参阅密钥保管库集成的先决条件

重要

将密钥保管库证书添加到 API 管理实例时,必须有权列出密钥保管库中的机密。

注意

在 API 管理中使用密钥保管库证书时,请注意不要删除证书、密钥保管库或用于访问密钥保管库的托管标识。

将密钥保管库证书添加到 API 管理:

  1. Azure 门户,导航到 API 管理实例。

  2. 在“安全性”下,选择“证书” 。

  3. 选择“证书”>+ 添加” 。

  4. 在“Id”中,输入所选的名称。

  5. 在“证书”下,选择“密钥保管库” 。

  6. 输入密钥保管库证书的标识符,或选择“选择”来选择密钥保管库中的证书。

    重要

    如果你自己输入密钥保管库证书标识符,请确保它不包含版本信息。 否则,在密钥保管库中更新后,证书不会在 API 管理中自动轮换。

  7. 在“客户端标识”中,选择一个系统分配的托管标识,或一个现有的用户分配的托管标识。 了解如何在 API 管理服务中添加或修改托管标识

    注意

    标识需要从密钥保管库获取和列出证书的权限。 如果你尚未配置对密钥保管库的访问权限,则 API 管理会提示你,你可以让其自动为标识配置必要的权限。

  8. 选择“添加” 。

    Screenshot of adding a key vault certificate to API Management in the portal.

  9. 选择“保存”。

上传证书

将客户端证书上传到 API 管理:

  1. Azure 门户,导航到 API 管理实例。

  2. 在“安全性”下,选择“证书” 。

  3. 选择“证书”>+ 添加” 。

  4. 在“Id”中,输入所选的名称。

  5. 在“证书”下,选择“自定义” 。

  6. 浏览以选择证书 .pfx 文件并输入其密码。

  7. 选择“添加” 。

    Screenshot of uploading a client certificate to API Management in the portal.

  8. 选择“保存”。

注意

如果只想使用证书向 API 管理验证客户端,可以上传 CER 文件。

启用 API 管理实例,以接收和验证客户端证书

开发人员、基本、标准和高级层

若要在开发人员层、基本层、标准层或高级层中通过 HTTP/2 接收和验证客户端证书,必须在“自定义域”边栏选项卡上启用“协商客户端证书”设置,如下所示。

Negotiate client certificate

消耗层

若要在“消耗”层中接收并验证客户端证书,必须在“自定义域”边栏选项卡上启用“请求客户端证书”设置,如下所示。

Request client certificate

用于客户端证书的策略

使用 validate-client-certificate 策略验证客户端证书的一个或多个属性,该客户端证书用于访问托管在 API 管理 API 实例中的 API。

配置策略以验证一个或多个属性,包括证书颁发者、主题、指纹、是否根据联机吊销列表验证证书等。

使用上下文变量的证书验证

还可使用 context 变量创建策略表达式以检查客户端证书。 以下各节中的示例显示了使用 context.Request.Certificate 属性和其他 context 属性的表达式。

注意

如果通过应用程序网关公开 API 管理网关终结点,相互证书身份验证可能无法正常工作。 这是因为应用程序网关充当第 7 层负载均衡器,会与后端 API 管理服务建立一个不同的 SSL 连接。 由于此原因,客户端在初始 HTTP 请求中附加的证书不会转发到 APIM。 但有一个解决方法,就是使用服务器变量选项来传输证书。 有关详细说明,请参阅相互身份验证服务器变量

重要

  • 从 2021 年 5 月开始,仅当 API 管理实例的 hostnameConfigurationnegotiateClientCertificate 属性设置为 True 时,context.Request.Certificate 属性才会请求证书。 negotiateClientCertificate 默认设置为 false。
  • 如果在客户端中禁用了 TLS 重新协商,可能会在使用 context.Request.Certificate 属性请求证书时看到 TLS 错误。 如果发生这种情况,请在客户端中启用 TLS 重新协商设置。

检查颁发者和使用者

可以将以下策略配置为检查客户端证书的颁发者和使用者:

<choose>
    <when condition="@(context.Request.Certificate == null || !context.Request.Certificate.Verify() || context.Request.Certificate.Issuer != "trusted-issuer" || context.Request.Certificate.SubjectName.Name != "expected-subject-name")" >
        <return-response>
            <set-status code="403" reason="Invalid client certificate" />
        </return-response>
    </when>
</choose>

注意

若要禁止检查证书吊销列表,请使用 context.Request.Certificate.VerifyNoRevocation() 而不是 context.Request.Certificate.Verify()。 如果客户端证书是自签名证书,则必须将根(或中间)CA 证书上传到 API 管理,context.Request.Certificate.Verify()context.Request.Certificate.VerifyNoRevocation() 才能正常工作。

检查指纹

可以将以下策略配置为检查客户端证书的指纹:

<choose>
    <when condition="@(context.Request.Certificate == null || !context.Request.Certificate.Verify() || context.Request.Certificate.Thumbprint != "DESIRED-THUMBPRINT-IN-UPPER-CASE")" >
        <return-response>
            <set-status code="403" reason="Invalid client certificate" />
        </return-response>
    </when>
</choose>

注意

若要禁止检查证书吊销列表,请使用 context.Request.Certificate.VerifyNoRevocation() 而不是 context.Request.Certificate.Verify()。 如果客户端证书是自签名证书,则必须将根(或中间)CA 证书上传到 API 管理,context.Request.Certificate.Verify()context.Request.Certificate.VerifyNoRevocation() 才能正常工作。

针对已上传到 API 管理的证书检查指纹

以下示例演示如何针对已上传到 API 管理的证书,检查客户端证书的指纹:

<choose>
    <when condition="@(context.Request.Certificate == null || !context.Request.Certificate.Verify()  || !context.Deployment.Certificates.Any(c => c.Value.Thumbprint == context.Request.Certificate.Thumbprint))" >
        <return-response>
            <set-status code="403" reason="Invalid client certificate" />
        </return-response>
    </when>
</choose>

注意

若要禁止检查证书吊销列表,请使用 context.Request.Certificate.VerifyNoRevocation() 而不是 context.Request.Certificate.Verify()。 如果客户端证书是自签名证书,则必须将根(或中间)CA 证书上传到 API 管理,context.Request.Certificate.Verify()context.Request.Certificate.VerifyNoRevocation() 才能正常工作。

提示

中所述的客户端证书死锁问题可以通过多种方式表现出来,例如:请求冻结、请求在超时后生成 403 Forbidden 状态代码、context.Request.Certificatenull。 此问题通常会影响内容长度约为 60KB 或更大的 POSTPUT 请求。 若要防止出现此问题,请在“自定义域”边栏选项卡上为所需主机名启用“协商客户端证书”设置,如本文档的第一个图像所示。 在“消耗”层中,此功能不可用。

后续步骤