ASP.NET Web API

具有 Windows Azure AD 和 Microsoft OWIN 组件的安全 ASP.NET Web API

Vittorio Bertocci

随着 Web API 角色的重要性日益增加,在可能暴露敏感数据和操作的高价值方案中确保能够信心十足地使用 Web API 的需求也愈加迫切。

我们可以清楚地看到,整个行业都在寻找一种解决方案,以便为依赖 OAuth 2.0 标准的 REST API 提供保护。 但在实践中,关于应该在项目层面上做些什么,并没有提供详细的指导。 此外,Microsoft .NET Framework 中用于保护通信的现有类和工具设计用于特定应用程序类型(基于回发的 Web UX 应用程序)。 它们不适用于 Web API 及其支持的多客户端方案。 因此,保护 Web API 的工作在相当程度上已成为一种手工活动。 这些保护工作不见得不安全,但各解决方案之间的差异很大,需要过多的自定义代码。

随着 Visual Studio 2013 的发布,您可以将这些烦恼全都抛诸脑后。 此版本引入了来自 Microsoft Open Web Interface for .NET (OWIN) 组件的创新 ASP.NET 工具和安全中间件,它们可为您的 Web API 提供直接的保护。 通过新型 ASP.NET 工具和模板,您可以对 Web API 项目进行配置,使之将身份验证直接外包给 Windows Azure Active Directory (AD),从而发出本地项目和 Windows Azure AD 相应条目中的必要代码。

在本文中,我将向大家介绍如何利用 Visual Studio 2013 的这些新功能创建受 Windows Azure AD 保护的简单 Web API。 我还将向大家展示如何创建一个测试用客户端,从而演示实际使用中的 API。 我还将简单探讨一下后台情况,如果您想要更深入探究此方案的更高级方面,可将其作为起点。

请提供凭据

归根结底,身份验证的功能是在调用方向服务器发送消息时,要求调用方提供可验证其身份或检索其属性的某种凭据。 服务器随后使用这些信息进行授权:确定是否应授予访问权限,以及在哪些方面授予访问权限。

资源通常将大部分身份验证功能转移给外部服务提供商,通常称为颁发机构或身份提供者。 这些提供者负责繁重的任务,例如让用户登录、分配凭据、处理生命周期流程(如密码恢复)、提供用于用户身份验证的 UI、验证多个协议上的凭据、多身份验证因素管理、欺诈检测等。

将这些功能放到一边,剩下的唯一身份验证任务是确认身份验证是否在所选颁发机构成功通过。 此项工作通常涉及对安全令牌的检查,安全令牌是身份验证成功后颁发机构向调用方发放的数据片段。

安全令牌通常根据特定的格式创建,由将明确识别颁发机构的密钥进行数字签名,并且包含一些将此令牌唯一绑定到目标资源的数据。 资源在收到请求时,会寻找随附的令牌。 如果发现一个符合所需验证属性的令牌,调用方即通过身份验证。

在这一详细级别上,此模式通用性很高,以致于其能够描述许多不同的身份验证方法。 我将通过为具体实体分配高级角色将其应用于此方案。

资源 资源将是我需要保护的 ASP.NET Web API 2 项目。 您可以在更细的粒度上应用身份验证要求。 例如,您可以定义操作的子集来进行保护,并让其他操作接受匿名调用方。

颁发机构 我将对 Web API 进行配置,使之将身份验证需求转移到 Windows Azure AD,后者是面向每个 Windows Azure 订户的平台即服务 (PaaS) 产品。 Windows Azure AD 专门用于支持基于云的应用程序工作负载。 此服务保存有关用户(属性和凭据)和组织结构的信息。 您可以将其数据与 Windows Server Active Directory 进行同步(如果您选择这样做),或者将数据全部放在云中,无需内部部署基础结构。

几乎每种在线 Microsoft 服务(Office 365、Intune 和 Windows Azure)都利用 Windows Azure AD 来满足其身份验证和目录需求。 由于有了开放式标准以及对常见协议的支持,您可以从几乎任何应用程序(Web UX、Web API、本机客户端、服务器至服务器等)和平台连接到 Windows Azure AD。 我将演示如何在 Windows Azure AD 中注册应用程序,以及利用其 OAuth 2.0 端点。

令牌格式与验证 OAuth 2.0 规范未对任何具体令牌格式作强制要求,但针对 REST 方案的 JSON Web 令牌 (JWT) 格式 (bit.ly/14EhlE8) 已成为一种事实标准。 Windows Azure AD 和 Microsoft OWIN 组件均支持 OAuth 2.0 流中的 JWT 格式。 我之所以提到这一点,主要是为了提供一些背景信息。 JWT 获取和验证机制均由中间件负责,令牌格式对应用程序代码是透明的。

客户端 当资源依靠颁发机构来处理身份验证时,它实际上与客户端实现了分离。 用户(以及客户端应用程序)如何获得令牌则成为用户与颁发机构之间的事情。 这对代码的可维护性非常有益,但如果您想看看 API 的实际效果,仍需要设置一个客户端。 您将了解如何在 Windows Azure AD 中注册支持 Web API 的本机客户端,以及如何使用 Windows Azure AD Authentication Library (ADAL) 使 .NET 富客户端应用程序能够通过 Windows Azure AD 对用户进行身份验证,以及获得令牌,以保护对 Web API 的调用。

图 1 显示我将构建的解决方案的各个元素。 如果您此时看不懂其中的一些标签,不要担心:当我演示该解决方案的开发过程时,将一一做介绍。

The Architecture of the End-to-End Solution
图 1 端到端解决方案的体系结构

创建 Web API 项目

要创建 Web API 项目,需要用到 Visual Studio 2013 中新的 ASP.NET 工具和模板。 打开 Visual Studio,新建一个 ASP.NET Web 应用程序项目。 在新建项目对话框中,选择 Web API 模板。 单击“更改身份验证”按钮。

将显示可为 Web API 选择的现成的身份验证方式,如图 2 所示。 选择组织帐户,您可以借此将 Windows Azure AD 作为颁发机构。 (有关所有选项的更多信息,请参见 bit.ly/1bhWngl。)此处的目标是收集从身份管理角度而言非常重要的 Web API 特性信息,并决定配置哪个 Windows Azure AD 实例(通常称为“租户”)来处理身份验证。

The Organizational Accounts Authentication Dialog
图 2 组织帐户身份验证对话框

第一个下拉列表确定是只有一个 Windows Azure AD 租户(业务线应用程序的典型情况)还是有多个 Windows Azure AD 租户(适用于软件即服务 [SaaS] 应用程序)使用该应用程序。 如果您的应用程序将由您公司中的员工使用,通常应选择“单个组织”;或者,如果该应用程序将由来自多个公司的用户访问,通常应选择“多个组织”。 虽然如此,这两个选项当前仅可用于 ASP.NET WebForms 和 ASP.NET MVC 应用程序。 当前,Web API 项目模板仅支持“单个组织”选项。

“域”文本框标识哪个 Windows Azure AD 租户应注册您的应用程序。 其内容一般为与您 Windows Azure 订阅关联的企业目录,但实际您拥有管理凭据的任何目录都可以(稍后详做说明)。 在创建时,每个 Windows Azure AD 租户都有一个采用 yourorganization.onmicrosoft.com 格式的关联三级域。 一般而言,您应将该租户与您已经拥有的一个或多个域相关联。 在图 2 的示例中,我使用自己的域 cloudidentity.net

“访问级别”下拉列表指定此应用程序针对该目录应具有哪些访问权限。 默认值“单一登录”可使该目录为您的应用程序发放令牌。 这简单确认了该应用程序已注册。 颁发机构不会为未注册的应用程序发放令牌,即使成功通过了身份验证也是如此。

其他访问权限包括“读取目录数据”和“读取和写入目录数据”,它们分别可使应用程序查询该目录和修改其内容。 其通过 Graph API 来执行此操作,Graph API 是一种基于 REST 的编程接口,可使来自具有 HTTP 堆栈的任何平台的应用程序获得对该目录的委托访问权。 我使用默认的“单一登录”访问级别,并且不演示 Web API 项目中的“图形”。 但这是 Windows Azure AD 的一项非常强大的功能。 有关 Windows Azure AD Graph API 的更多信息,请参阅 bit.ly/1aByRLS

输入与 Windows Azure AD 租户关联的域后,单击“确定”,生成该项目。 该工具将执行两项任务:

  1. 它将访问所选 Windows Azure AD 租户,并且添加一个描述正在创建的应用程序的条目。
  2. 它将发出 Web API 项目的代码,添加来自 Microsoft OWIN 组件的必要安全中间件以处理 Windows Azure AD 身份验证,以及生成必要的初始化代码以便根据所选 Windows Azure AD 租户验证传入令牌。

第一个任务是通过 Graph API 执行的(Visual Studio 本身就是 Graph API 的一个客户端)。 为了在目录中添加描述正在创建的应用程序的条目,该工具需要从该目录中获得一个令牌,证明您具有在该目录中进行写入的必要权限。 正因为如此,单击“确定”后 Visual Studio 会向您显示图 3 所示的身份验证提示。

Account Sign in Authentication Prompt
图 3 帐户登录身份验证提示

您要做的只是输入该目录的管理员凭据。 该工具将验证您是否具有执行所需操作的必要权限。 单击“确定”时,Visual Studio 会联系 Windows Azure AD 并创建项目文件。

您现在有了配置好的 Web API,可以规定只有来自指定 Windows Azure AD 租户的调用方才能获得访问权限。 让我们进一步了解一下。

转到“解决方案资源管理器”窗格,那里的根目录中会显示一个名为 Startup.cs 的文件。 如果您读过 Howard Dierking 上月发表的《Katana 项目入门》(msdn.microsoft.com/magazine/dn451439),应当知道在应用程序启动时会调用此文件中的类。 本例中的实现非常简单:

public partial class Startup
{
  public void Configuration(IAppBuilder app)
  {
    ConfigureAuth(app);
  }
}

因为这是标准实现,您应当能在 App_Start 解决方案文件夹下找到某个类中定义的 ConfigureAuth 方法。 它实际上就是 Startup.Auth.cs 文件。 内容如下所示:

public partial class Startup
{
  public void ConfigureAuth(IAppBuilder app)
  {
    app.UseWindowsAzureActiveDirectoryBearerAuthentication(
      new WindowsAzureActiveDirectoryBearerAuthenticationOptions
      {
        Audience = ConfigurationManager.AppSettings["ida:Audience"],
        Tenant = ConfigurationManager.AppSettings["ida:Tenant"]
      });
  }
}

app.Use* 命名约定建议该方法在 OWIN 管道中添加一个中间件实现。 此情况下,添加的中间件负责检查传入请求,查看 HTTP 头身份验证是否包含安全令牌。 您就是通过这种方式根据 OAuth 2.0 持有者令牌规范为请求提供保护(请参阅 bit.ly/W4OqA3)。 如果找到令牌,则会通过一些标准检查对它进行验证:该令牌是否是由预期的颁发机构颁发? 该令牌在传输中是否遭到篡改? 该令牌是否已过期?

如果此令牌一切正常,则中间件会在一个主体中投射其内容,将该主体指定给当前用户,并将控制权放给管道中的下一个元素。 如果此令牌没有通过这些检查,则中间件会发回相应的错误代码。

如果没有令牌,则中间件只是让调用通过,而不创建主体。 一般采用授权筛选器形式的授权逻辑利用主体(及其内容)是否存在来确定应服务此请求还是拒绝此请求的访问。

传递到中间件的唯一参数 WindowsAzureActiveDirectoryBearerAuthenticationOptions 提供用于确定令牌有效性的设置。 它在项目创建过程中捕获原始值,然后将它们保存在 web.config 文件中。 Audience 值是使 Windows Azure AD 识别 Web API 所依据的标识符。 携带不同 Audience 的任何令牌用于其他资源,应予以拒绝。

Tenant 属性指示用于外包身份验证的 Windows Azure AD 租户。 中间件使用这些信息访问该租户,并读取用于确定令牌有效性的其他所有属性(例如应使用哪个密钥验证该令牌的签名)。

这几行自动生成的代码就是通过 Windows Azure AD 对调用方进行身份验证所需的全部代码。 在 Web API 项目上要做的事情只剩一件,那就是利用 [Authorize] 修饰您要保护的方法。 应将其添加到 ValuesController 中的所有方法。 (为简便起见,我使用了模板随附的默认控制器。)

现在,您如何验证 Web API 的行为符合预期? 最简便的方法是创建测试客户端。

注册本机客户端

就像 Windows Azure AD 不会为尚未注册的 Web API 办法令牌一样,Windows Azure 也要求请求令牌的所有客户端进行注册。 为了为特定 Web API 获取令牌,还必须为已注册的客户端明确授予对 Windows Azure AD 中 Web API 的访问权。 这些设计时设置必须在任何用户尝试进行身份验证前就绪。 现在,我将展示如何使用 Windows Azure 门户注册客户端应用程序,并创建将其绑定到所创建的 Web API 的权限。

首先访问 Windows Azure 门户。 我将对先前使用的同一帐户进行身份验证。 为节省时间,我将直接导航到我的 Windows Azure AD 租户所对应的 Windows Azure 门户。 通过将租户域添加到普通门户地址,可以获得此 URL,我使用的是 https://manage.windowsazure.com/cloudidentity。 net。

导航到租户特定的 URL 比从一般登录页面寻找“使用您的组织帐户登录”更快。 注意,如果正在使用的 Windows Azure AD 不是您的 Windows Azure 订阅的管理员或共同管理员,则需要使用您的普通凭据(Microsoft 帐户或其他组织实体)登录到该门户。

此门户加载后,从可用服务列表中选择 Active Directory 图标,单击您的目录,然后单击“应用程序”选项卡。 您将看到包含所创建的全部应用程序的列表,包括新 Web API 项目的对应条目。 要新建一个条目,请从页面底部的命令栏单击“添加”按钮。 您将看到如图 4 所示的对话框。 从技术上讲,您能够定义几乎任何一种应用程序,作为您的 Web API 的有效客户端。 选择一个本机客户端应用程序,然后转到下一个屏幕。

The First Step of the Add Application Wizard on the Windows Azure Active Directory Portal
图 4 Windows Azure Active Directory 门户上添加应用程序向导的第一步

下一个也就是最后一个屏幕要求您输入该应用程序的重定向 URI。 此 URI 只是在 OAuth 2.0 令牌获取流程中使用的标识符,用于向调用方发出信号,指示身份验证流程的交互部分已完成。 令牌获取流程的其余部分将继续进行,无需用户输入。 根据开发本机客户端所在的平台,您可能需要处理不同的约束。 例如,如果您要使用某些功能,Windows 应用商店的应用将要求使用 ms-app:// 协议架构(详细信息见 bit.ly/13KrM6i)。

本例中,我将编写一个不太严格的典型 .NET 桌面应用程序。 使用任何有效的 URI 都可以。 我使用的是 https://cloud­identity。 net/myWebAPItestclient,以方便我记忆此客户端的用途。

一完成此应用程序输入,我就会看到如图 5 所示的页面。

The Quick Start Page
图 5 快速启动页面

“更新您的代码”部分提供了编写客户端应用程序时将需要的信息。 其中包括重定向 URI 和客户端 ID(简单标识符)的设置,在创建向颁发机构提出的令牌请求时将需要它们。

“配置对 Web API 的访问”部分提供了一个可转到特定门户区域的链接,在该区域中,您可以指定客户端应能够访问的 API。 如果点击此链接,将进入图 6 所示的应用程序属性页面。 底部显示一个下拉列表,可在其中指定客户端需要访问的 Web API。 请注意,该下拉列表同时列出了您定义的应用程序和内置 API,具体讲,是 Windows Azure AD Graph API。

The Native Client Application Properties Page on the Windows Azure Active Directory Portal
图 6 Windows Azure Active Directory 门户上的本机客户端应用程序属性页面

选择 Web API 项目条目,然后单击“保存”。 执行此操作后,Windows Azure AD 中的所有工作即告完成,客户端就能够为您的服务获取令牌了。 保持浏览器页面打开,因为您将需要该页面将显示的一些数据。

创建一个简单的客户端项目并测试 Web API

您现在终于做好全部准备,可以创建一个测试客户端,试一下经过身份验证的 Web API 了。 您可在提供 HTTP 堆栈的任何平台上创建几乎任何客户端类型。 为简化获取和维护令牌的任务,Microsoft 提供了 ADAL,通过它可轻松针对 Active Directory(Windows Azure 和 Windows Server)进行身份验证,无需成为身份验证协议方面的专家。

Microsoft .NET Framework 库已发布,现为面向 Windows 应用商店的开发人员预览版。 (有关如何使用 Windows 应用商店的应用实现此方案的教程,请参阅 bit.ly/17YtYVg)。最终将发布面向所有主要客户端平台的版本。

由于 .NET 版本已经全面上市,在此我将使用该版本。 除不同堆栈间的语法差异外,您在这里学到的知识同样适用于其他平台和应用程序类型。 如果您等不及,请记住此处所述方案中的所有通信均遵循开放式标准,并且已详细加以记录。 您能够非常轻松地对令牌请求逻辑进行编码。 有关使用 Windows Phone 8 的示例,请参阅 bit.ly/YatATk

此项目面向同一解决方案中新的 Windows Presentation Foundation (WPF) 应用程序,但您能够选择需与用户交互运行的任何项目类型。 创建项目后,我将添加一个按钮和一个单击事件处理程序,用于触发 Web API 调用逻辑。

ADAL 作为 NuGet 程序包分发。 要将其添加到客户端项目,只需从 Visual Studio 中的“工具”菜单打开程序包管理器控制台,然后键入 Install-Package Microsoft.IdentityModel.Clients.ActiveDirectory -Version 1.0.0。 现在将图 7 中的代码添加到单击事件处理程序中。

图 7 将添加到单击事件处理程序中的代码

private async void btnCall_Click(object sender, RoutedEventArgs e)
{
  // Get token
  AuthenticationContext ac = new AuthenticationContext(
    "https://login.windows.net/cloudidentity.net");
  AuthenticationResult ar =
    ac.AcquireToken("https://cloudidentity.net/WindowsAzureADWebAPITest",
    "a4836f83-0f69-48ed-aa2b-88d0aed69652",
    new Uri("https://cloudidentity.net/myWebAPItestclient"));
  // Call Web API
  string authHeader = ar.CreateAuthorizationHeader();
  HttpClient client = new HttpClient();
  HttpRequestMessage request = new HttpRequestMessage(
    HttpMethod.Get, "https://localhost:44353/api/Values");
  request.Headers.TryAddWithoutValidation("Authorization", authHeader);
  HttpResponseMessage response = await client.SendAsync(request);
  string responseString = await response.Content.ReadAsStringAsync();
  MessageBox.Show(responseString);
}

如果您觉得这段代码看起来有些复杂,请别担心。 一会儿一切就都清楚了。

第一行初始化新的 AuthenticationContext。 在该应用程序的代码中,AuthenticationContext 实例代表要使用的颁发机构。 在这个示例中,该颁发机构是由 URI https://login.windows. net/[mydomain] 代表的我的 Windows Azure AD 租户。 您将使用相同逻辑,通过传递内部部署目录的 Active Directory 联合身份验证服务 (AD FS) 服务器的地址,连接到该目录(有关详细信息,请参阅 bit.ly/1d553F0)。

第二行向 AuthenticationContext 请求一个令牌。 创建令牌请求的方式有很多 — 每个方案和应用程序类型均需要不同的参数。 在本例中,我要指定希望获得令牌的资源是我的 Web API。 因此,我传递了其 Windows Azure AD 条目的标识符。 这是用作 Web API 项目中的 Audience 的同一值。 然后,因为这是请求令牌的本机客户端,因此我需要通过在浏览器中保持打开的应用程序配置页面,传入我的客户端的客户端 ID 和重定向 URI。

这就是获得令牌需要执行的所有操作。 此代码的其余部分根据 OAuth 2.0 规范将该令牌置于正确的 HTTP 头中,然后执行实际调用。

现在赶快试试这个解决方案吧。 将该解决方案更改为一次启动所有项目后,按 F5。 执行到对 AcquireToken 的调用后,您就会看到图 8 所示的身份验证对话框。 ADAL 负责联系正确的端点,并在弹出对话框中呈现服务器提供的身份验证体验,无需您编写任何 UI 代码。

The Active Directory Authentication Library Authentication Dialog
图 8 Active Directory 身份验证库身份验证对话框

当我提供目录租户中的任何有效用户的凭据时,我就会获得一个令牌。 后续代码在请求头中将该令牌提供给 Web API。 安全中间件对其进行验证。 由于所有验证参数都匹配,因此它随结果发回 HTTP 状态代码 200,成功完结此方案的概念证明。

作为有趣的试验,请再次单击该按钮。 此时您将看到,您会立即获得一个令牌,而不会看到任何提示。 这是因为 ADAL 有一个跟踪这些令牌的内置令牌缓存。 它甚至会在必要时以静默方式刷新过期的令牌。

下一步该怎么做?

我只是揭开了 Web API、Microsoft OWIN 组件、Windows Azure AD 和 ADAL 众多功能的冰山一角。 Windows Azure AD 发放的令牌不仅仅是身份验证的证明。 它们携带了丰富的用户信息,您能够从用户的主体中轻松访问这些信息,并将它们用于高级授权逻辑。 身份验证流程涉及的远非此处所示的用户名和密码。

从多重身份验证因素,到联合方案中的无缝单一登录,一切皆有可能。 与 Graph API 相集成可使您访问丰富的功能。 从简单的人员选取器功能,到高级的组织结构爬网,您可以通过查询目录获得不仅限于已经过身份验证的用户的信息。

这些功能可添加到基于云的 Web API 的功能中。 到现在为止,您必须在企业防火墙后端本地运行这些功能。 在云和内部部署的功能对称性的一个最明显示例中,您还可以对 Windows Server Active Directory 使用类似的代码。

当然,您还可以将 Web API 发布到 Windows Azure,无需更改一行代码 — 这些身份验证功能将保持正常工作。 您需要做的唯一更改在客户端项目上进行。 服务的 URL 将根据新的应用程序位置而改变。 但获取令牌的逻辑可保持完全相同,因为不必将资源标识符绑定到该资源的物理地址。

Vittorio Bertocci 是 Windows Azure AD 团队的首席项目经理,负责开发人员体验工作。Bertocci 通过其书籍、在重大会议上的演讲、博客 (cloudidentity.com) 以及 Twitter 源 (twitter.com/vibronet) 进行了长达十年的身份与开发推广活动,并因此在开发人员社区中广为人知。

衷心感谢以下 Microsoft 技术专家对本文的审阅:Howard Dierking 与 Daniel Roth
Howard Dierking 是 Windows Azure Frameworks and Tools 团队的项目经理,工作重点是 ASP.NET、NuGet 和 Web API。 Dierking 以前是 MSDN 杂志的主编,还负责 Microsoft Learning 的开发者认证计划。 在 Microsoft 就职之前,他有着 10 年的开发人员和应用程序架构师工作经验,工作重点是分布式系统。

Daniel Roth 是 Windows Azure Application Platform 团队的高级项目经理,目前负责 ASP.NET Web API 方面的工作。 在负责 ASP.NET 之前,他自 WCF 最初随 .NET Framework 3.0 推出时便开始负责 WCF。 他热衷于让框架变得简单易用,从而为客户带来快乐。