保护 API

作为开发人员,在保护 API 时,重点是授权。 若要调用资源的 API,应用程序需要获取应用程序授权。 资源本身必须强制实施授权。 在本文中,将了解通过注册保护 API、定义权限和同意,以及强制实施访问权限,以实现“零信任”目标的最佳做法。

注册受保护的 API

若要使用 Microsoft Entra ID (Microsoft Entra ID) 保护 API,首先“注册 API”,之后可以“管理已注册的 API”。 在 Microsoft Entra ID 中,API 是具有特定应用注册设置的应用,该设置将其定义为可以授权其他应用程序访问的资源或 API。 在 Microsoft Entra 管理中心,Microsoft 标识开发人员、应用注册是租户中的 API,既可以是业务线 API,也可以是 SaaS 提供商的服务,这些服务都有 Microsoft Entra ID 保护的 API。

在注册期间,你将定义调用应用程序将如何引用 API,及其委派权限应用程序权限。 应用注册可以表示同时具有客户端应用程序和 API 的解决方案。 但是,在本文中,我们将解决独立资源公开 API 的场景。

通常情况下,API 不执行身份验证或直接请求授权。 API 将验证通过调用应用提供的令牌。 API 没有交互式登录,因此无需注册重定向 URI 或应用程序类型等设置。 API 从调用这些 API 的应用程序获取令牌,而不是通过与 Microsoft Entra ID 交互。 对于 Web API,使用 OAuth2 访问令牌进行授权。 Web API 验证持有者令牌,以授权调用方。 不接受 ID 令牌作为权限证明。

默认情况下,Microsoft Entra ID 会将 User.Read 添加到任何新应用注册的 API 权限。 你将删除大多数 Web API 的此权限。 仅在 API 调用另一个 API 时,Microsoft Entra ID 才需要 API 权限。 如果 API 不调用另一个 API,在注册 API 时删除 User.Read 权限。

你将需要 API 的唯一标识符(称为“应用程序 ID URI”),而需要访问 API 的客户端应用会请求调用 API 的权限。 应用程序 ID URI 需要在所有 Microsoft Entra 租户中是唯一的。 可以使用 api://<clientId>(门户中的默认建议),其中 <clientId> 是已注册 API 的应用程序 ID。

要为调用 API 的开发人员提供更易记名称,可以使用 API 的地址作为应用程序 ID URI。 例如,可以使用 https://API.yourdomain.com,其中 yourdomain.com 必须是 Microsoft Entra 租户中已配置发布者域。 Microsoft 验证你是否拥有域的所有权,以便你能将其用作 API 的唯一标识符。 你不需要在此地址有代码。 API 可以随心所欲地使用,但最好是使用 API 的 HTTPS 地址作为应用程序 ID URI。

定义具有最低特权的委派权限

如果 API 由具有用户的应用程序调用,则你必须至少定义一个委托权限(请参阅应用注册“公开 API”中的添加范围

API 提供对组织数据存储的访问权限,会吸引想要访问该数据的攻击者注意。 使用“最低权限访问”的零信任原则设计权限,而不是只拥有一个委派权限。 如果所有客户端应用都以完全特权访问权限开始,则之后很难进入最低特权模型。

通常,开发人员会陷入使用单个权限的模式,如“以用户身份访问”或“用户模拟”(这是一个常见短语,尽管在技术上并不准确)。 此类的单个权限仅允许对 API 进行完全特权访问。

声明最低特权范围,使应用程序不会容易受到入侵或被用于执行你从未想过的任务。 在“API 权限”中定义多个范围。 例如,将范围与读取和更新数据分开并考虑提供只读权限。 “写入访问权限”包括执行创建、更新和删除操作的特权。 客户端永远不应要求对只读数据提供写入访问权限。

你的 API 处理敏感数据时,请考虑使用“标准”和“完全”访问权限。 限制敏感属性,使标准权限不允许访问(例如 Resource.Read)。 然后实现“完全”访问权限(例如 Resource.ReadFull),这会返回属性和敏感信息。

始终评估你所请求的权限,确保这些权限是完成工作而需要的绝对最低的一组特权。 避免请求更高的特权权限。 而是为每个核心场景创建单个权限。 有关此方法的良好示例,请参阅 Microsoft Graph 权限参考。 找到并使用适当数量的权限来满足你的需求。

作为范围定义的一部分,确定在特定范围内执行操作的范围是否需要管理员同意

作为 API 设计师,可以提供有关哪些范围可以安全地要求用户同意的指导。 但是,租户管理员可以配置租户,以便所有权限都需要获得管理员同意。 如果将范围定义为需要管理员同意,则权限将始终需要管理员同意。

决定用户同意还是管理员同意时,有两个主要注意事项:

  1. 权限背后的操作范围是否影响多个用户。 如果权限允许用户选择哪个应用程序只能访问自己的信息,则用户同意可能适用。 例如,用户可以同意为电子邮件选择首选应用程序。 但是,如果权限背后的操作涉及多个用户(例如,查看其他用户的完整用户配置文件),则将该权限定义为需要管理员同意。

  2. 权限背后的操作范围是否范围广泛。 例如,广泛的范围是指,当某个权限使应用能够更改租户中的所有内容,或执行可能不可逆的内容时。 广泛的范围表示你需要管理员同意,而不是用户同意。

如果你有疑问,请采取保守的做法并要求管理员同意。 清楚、简洁地描述权限字符串中同意的后果。 假设读取说明字符串的个人对 API 或产品不熟悉。

避免以更改权限语义的方式将 API 添加到现有权限。 重载现有权限会削弱客户端以前授予同意的理由。

定义应用程序权限

在生成“非用户应用程序”时,无需提示用户输入用户名和密码或进行多重身份验证 (MFA)。 如果 API 将由没有用户的应用程序调用(例如工作负载、服务和守护程序),则必须为 API 定义“应用程序权限”。 定义应用程序权限时,将使用“应用程序角色”,而不是使用范围。

与委托的权限一样,你将提供精细的应用程序权限,以便调用 API 的工作负载可以遵循最低权限访问,并符合零信任原则。 避免仅发布一个应用角色(应用权限)和一个范围(委托的权限),或向每个客户端公开所有操作。

工作负载使用“客户端凭证”进行身份验证,并使用“.default 范围”请求令牌,如以下示例代码所示。

// With client credentials flows the scopes is ALWAYS of the shape "resource/.default", as the 
// application permissions need to be set statically (in the portal or by PowerShell), 
// and then granted by a tenant administrator
string[] scopes = new string[] { "https://kkaad.onmicrosoft.com/webapi/.default" };
 
AuthenticationResult result = null;
try
{
  result = await app.AcquireTokenForClient(scopes)
    .ExecuteAsync();
  Console.WriteLine("Token acquired \n");
}
catch (MsalServiceException ex) when (ex.Message.Contains("AADSTS70011"))
{
  // Invalid scope. The scope has to be of the form "https://resourceurl/.default"
  // Mitigation: change the scope to be as expected
  Console.WriteLine("Scope provided is not supported");
}

当应用前没有用户,并且应用程序权限启用广泛的操作时,权限需要管理员同意。

强制实施访问权限

通过验证和解释访问令牌,该令牌可调用应用程序在 HTTPS 请求的授权标头中作为持有者令牌提供,确保 API 能够执行访问。 可以通过验证令牌、管理元数据刷新,以及强制实施范围和角色来强制实施访问权限,如以下部分所述。

验证令牌

API 收到令牌后,必须“验证令牌”。 验证可确保令牌来自未采样且未修改的正确发放者。 检查签名,因为你没有像使用 ID 令牌那样,直接从 Microsoft Entra ID 获取令牌。 在 API 从网络上不受信任的来源接收令牌后验证签名。

由于 JSON Web 令牌签名验证存在已知漏洞,请使用维护良好的成熟标准令牌验证库。 身份验证库(如 Microsoft.Identity.Web 或 Passport,如果正在构建节点)使用适当的步骤并减少已知漏洞。

(可选)扩展令牌验证。 使用租户 ID (tid) 声明来限制 API 可以获取令牌的租户。 使用 azpappid 声明以筛选可调用 API 的应用。 使用对象 ID (oid) 声明进一步缩小对单个用户的访问范围。

管理元数据刷新

始终确保令牌验证库能有效地管理所需的元数据。 在这种情况下,元数据就是 Microsoft 用来签署 Microsoft Entra 令牌的公钥(私钥对)。 当库验证这些令牌时,它们将从已知的互联网地址获取已发布的公共签名密钥列表。 确保宿主环境有适当的时机来获取这些密钥。

例如,旧库有时会被硬编码为每 24 小时更新一次这些公开签名密钥。 请考虑 Microsoft Entra ID 必须快速轮换这些密钥,并且你下载的密钥不包含新的轮换密钥的情况。 API 可能在等待其元数据刷新周期时,一天处于离线状态。 参考特定的元数据刷新指南,以确保正确获取元数据。 如果使用库,确保它在合理的时间范围内处理该元数据。

强制实施范围和角色

验证令牌后,API 将查看令牌中的声明,以确定其工作原理。

对于委托的权限令牌,让 API 检查范围 (scp) 声明,以查看应用程序同意执行的操作。 检查对象 ID (oid) 或主题密钥 (sub) 声明,以查看应用程序正为其工作的用户。

然后让 API 检查,以确保用户也有权限访问请求的操作。 如果 API 定义了用于分配给用户和组的角色,请让 API 检查令牌中的任何角色声明,并采取相应措施。 对于应用程序权限令牌,不会有范围 (scp) 声明。 相反,让 API 通过检查角色声明,确定工作负载收到的应用程序权限。

API 验证令牌和范围,并处理对象 ID (oid)、使用者密钥 (sub) 和角色声明后,API 可以返回结果。

后续步骤