使用 Azure Cosmos DB 文档数据库和 Xamarin.Forms 对用户进行身份验证

Azure Cosmos DB 文档数据库支持分区集合,这些集合可以跨越多个服务器和分区,同时支持无限制的存储和吞吐量。 本文介绍了如何将访问控制与分区集合相结合,使用户只能在 Xamarin.Forms 应用程序中访问自己的文档。

概述

创建分区集合时必须指定分区密钥,具有相同分区密钥的文档将存储在同一个分区中。 因此,指定用户标识作为分区密钥将导致分区集合只存储该用户的文档。 这还能确保 Azure Cosmos DB 文档数据库随着用户和项数量的增加而缩放。

必须向任何集合授予访问权限,Azure Cosmos DB for NoSQL 访问控制模型定义了两种类型的访问结构:

  • 主密钥支持对 Azure Cosmos DB 帐户中的所有资源进行完全管理访问,并在创建 Azure Cosmos DB 帐户时创建
  • 资源令牌捕获数据库用户与用户对特定 Azure Cosmos DB 资源(如集合或文档)所拥有的权限之间的关系

公开主密钥会使 Azure Cosmos DB 帐户面临恶意或疏忽使用的风险。 不过,Azure Cosmos DB 资源令牌提供了一种安全机制,支持客户端根据授予的权限读取、写入和删除 Azure Cosmos DB 帐户中的特定资源。

向移动应用程序请求、生成和交付资源令牌的典型方法是使用资源令牌代理。 下图显示了示例应用程序如何使用资源令牌代理管理对文档数据库数据的访问的大致概览:

文档数据库身份验证过程

资源令牌代理是一个中层 Web API 服务,托管在 Azure 应用服务中,拥有 Azure Cosmos DB 帐户的主密钥。 示例应用程序使用资源令牌代理管理对文档数据库数据的访问,如下所示:

  1. 登录时,Xamarin.Forms 应用程序会联系 Azure 应用服务以启动身份验证流。
  2. Azure 应用服务对 Facebook 执行 OAuth 身份验证流。 身份验证流完成后,Xamarin.Forms 应用程序会收到访问令牌。
  3. Xamarin.Forms 应用程序使用访问令牌从资源令牌代理请求资源令牌。
  4. 资源令牌代理使用访问令牌从 Facebook 请求用户标识。 然后,用户标识将用于向 Azure Cosmos DB 请求资源令牌,该令牌用于授予对已通过身份验证的用户的分区集合的读取/写入访问权限。
  5. Xamarin.Forms 应用程序使用资源令牌直接访问 Azure Cosmos DB 资源,权限由资源令牌定义。

注意

资源令牌过期时,后续的文档数据库请求将收到 401 未授权异常。 此时,Xamarin.Forms 应用程序应重新建立标识并请求新的资源令牌。

有关 Azure Cosmos DB 分区的更多信息,请参阅如何在 Azure Cosmos DB 中进行分区和缩放。 有关 Azure Cosmos DB 访问控制的更多信息,请参阅保护对 Azure Cosmos DB 数据的访问Azure Cosmos DB for NoSQL 中的访问控制

安装

将资源令牌代理集成到 Xamarin.Forms 应用程序的过程如下所示:

  1. 创建将使用访问控制的 Azure Cosmos DB 帐户。 有关详细信息,请参阅 Azure Cosmos DB 配置
  2. 创建 Azure 应用服务来托管资源令牌代理。 有关详细信息,请参阅 Azure 应用服务配置
  3. 创建 Facebook 应用以执行身份验证。 有关详细信息,请参阅 Facebook 应用配置
  4. 配置 Azure 应用服务以使用 Facebook 执行简易身份验证。 有关详细信息,请参阅 Azure 应用服务身份验证配置
  5. 配置 Xamarin.Forms 示例应用程序,以便与 Azure 应用服务和 Azure Cosmos DB 通信。 有关详细信息,请参阅 Xamarin.Forms 应用程序配置

注意

如果还没有 Azure 订阅,可以在开始前创建一个免费帐户

Azure Cosmos DB 配置

创建将使用访问控制的 Azure Cosmos DB 帐户的过程如下所示:

  1. 创建 Azure Cosmos DB 帐户。 有关详细信息,请参阅创建 Azure Cosmos DB 帐户
  2. 在 Azure Cosmos DB 帐户中,创建名为 UserItems 的新集合,指定分区键为 /userid

Azure 应用服务配置

在 Azure 应用服务中托管资源令牌代理的过程如下所示:

  1. 在 Azure 门户中,创建新的应用服务 Web 应用。 有关详细信息,请参阅在应用服务环境中创建 Web 应用

  2. 在 Azure 门户中,打开 Web 应用的“应用设置”边栏选项卡,并添加以下设置:

    • accountUrl - 该值应该是 Azure Cosmos DB 帐户的“密钥”边栏选项卡中的 Azure Cosmos DB 帐户 URL。
    • accountKey - 该值应该是 Azure Cosmos DB 帐户的“密钥”边栏选项卡中的 Azure Cosmos DB 主密钥(主要或辅助)。
    • databaseId - 该值应为 Azure Cosmos DB 数据库的名称。
    • collectionId - 该值应为 Azure Cosmos DB 集合的名称(在本例中为 UserItems)。
    • hostUrl - 该值应为应用服务帐户的“概述”边栏选项卡中的 Web 应用的 URL。

    以下屏幕截图演示了此配置:

    应用服务 Web 应用设置

  3. 将资源令牌代理解决方案发布到 Azure 应用服务 Web 应用。

Facebook 应用配置

创建 Facebook 应用以执行身份验证的过程如下所示:

  1. 创建 Facebook 应用。 有关详细信息,请参阅 Facebook 开发人员中心中的注册和配置应用
  2. 将 Facebook 登录产品添加到应用。 有关详细信息,请参阅 Facebook 开发人员中心中的将 Facebook 登录添加到应用或网站
  3. 按如下所示配置 Facebook 登录:
    • 启用客户端 OAuth 登录。
    • 启用 Web OAuth 登录。
    • 将“有效的 OAuth 重定向 URI”设置为应用服务 Web 应用的 URI,并追加 /.auth/login/facebook/callback

以下屏幕截图演示了此配置:

Facebook 登录 OAuth 设置

有关详细信息,请参阅向 Facebook 注册应用程序

Azure 应用服务身份验证配置

配置应用服务简易身份验证的过程如下所示:

  1. 在 Azure 门户中,导航到应用服务 Web 应用。

  2. 在 Azure 门户中,打开“身份验证/授权”边栏选项卡并执行以下配置:

    • 应启用应用服务身份验证。
    • 请求未通过身份验证时的操作应设置为“使用 Facebook 登录”

    以下屏幕截图演示了此配置:

    应用服务 Web 应用身份验证设置

应用服务 Web 应用也应配置为与 Facebook 应用通信,以启用身份验证流。 为此,可以选择 Facebook 标识提供者,并从 Facebook 开发人员中心的 Facebook 应用设置中输入 App ID 和 App Secret 值。 有关详细信息,请参阅将 Facebook 信息添加到应用程序

Xamarin.Forms 应用程序配置

配置 Xamarin.Forms 示例应用程序的过程如下所示:

  1. 打开 Xamarin.Forms 解决方案。
  2. 打开 Constants.cs 并更新以下常数的值:
    • EndpointUri - 该值应该是 Azure Cosmos DB 帐户的“密钥”边栏选项卡中的 Azure Cosmos DB 帐户 URL。
    • DatabaseName – 该值应为文档数据库的名称。
    • CollectionName - 该值应为文档数据库集合的名称(在本例中为 UserItems)。
    • ResourceTokenBrokerUrl - 该值应为应用服务帐户的“概述”边栏选项卡中的资源令牌代理 Web 应用的 URL。

发起登录

示例应用程序通过将浏览器重定向到标识提供者 URL 来发起登录过程,如以下示例代码所示:

var auth = new Xamarin.Auth.WebRedirectAuthenticator(
  new Uri(Constants.ResourceTokenBrokerUrl + "/.auth/login/facebook"),
  new Uri(Constants.ResourceTokenBrokerUrl + "/.auth/login/done"));

这会导致在 Azure 应用服务和 Facebook 之间启动 OAuth 身份验证流,并显示 Facebook 登录页面:

Facebook 登录

在 iOS 上按“取消”按钮或在 Android 上按“返回”按钮可取消登录,在这种情况下,用户保持未通过身份验证的状态,屏幕上的标识提供者用户界面也会移除

获取资源令牌

身份验证成功后,将触发 WebRedirectAuthenticator.Completed 事件。 下面的代码示例演示如何处理此事件:

auth.Completed += async (sender, e) =>
{
  if (e.IsAuthenticated && e.Account.Properties.ContainsKey("token"))
  {
    var easyAuthResponseJson = JsonConvert.DeserializeObject<JObject>(e.Account.Properties["token"]);
    var easyAuthToken = easyAuthResponseJson.GetValue("authenticationToken").ToString();

    // Call the ResourceBroker to get the resource token
    using (var httpClient = new HttpClient())
    {
      httpClient.DefaultRequestHeaders.Add("x-zumo-auth", easyAuthToken);
      var response = await httpClient.GetAsync(Constants.ResourceTokenBrokerUrl + "/api/resourcetoken/");
      var jsonString = await response.Content.ReadAsStringAsync();
      var tokenJson = JsonConvert.DeserializeObject<JObject>(jsonString);
      resourceToken = tokenJson.GetValue("token").ToString();
      UserId = tokenJson.GetValue("userid").ToString();

      if (!string.IsNullOrWhiteSpace(resourceToken))
      {
        client = new DocumentClient(new Uri(Constants.EndpointUri), resourceToken);
        ...
      }
      ...
    }
  }
};

成功身份验证的结果是一个访问令牌,该令牌是可用的 AuthenticatorCompletedEventArgs.Account 属性。 访问令牌被提取,并用于对资源令牌代理的 resourcetoken API 的 GET 请求。

resourcetoken API 使用访问令牌向 Facebook 请求用户标识,然后该标识继而被用来向 Azure Cosmos DB 请求资源令牌。 如果文档数据库中已经存在该用户的有效权限文档,则会检索该文档,并向 Xamarin.Forms 应用程序返回一个包含资源令牌的 JSON 文档。 如果不存在该用户的有效权限文档,则会在文档数据库中创建一个用户和权限,并从权限文档中提取资源令牌,以 JSON 文档的形式返回给 Xamarin.Forms 应用程序。

注意

文档数据库用户是与文档数据库相关联的资源,每个数据库可包含零个或多个用户。 文档数据库权限是与文档数据库用户相关联的资源,每个用户可能包含零个或多个权限。 权限资源提供对安全令牌的访问,用户在尝试访问文档等资源时需要使用该令牌。

如果 resourcetoken API 成功完成,它将在响应中发送 HTTP 状态代码 200 (OK),以及包含资源令牌的 JSON 文档。 以下 JSON 数据显示了典型的成功响应消息:

{
  "id": "John Smithpermission",
  "token": "type=resource&ver=1&sig=zx6k2zzxqktzvuzuku4b7y==;a74aukk99qtwk8v5rxfrfz7ay7zzqfkbfkremrwtaapvavw2mrvia4umbi/7iiwkrrq+buqqrzkaq4pp15y6bki1u//zf7p9x/aefbvqvq3tjjqiffurfx+vexa1xarxkkv9rbua9ypfzr47xpp5vmxuvzbekkwq6txme0xxxbjhzaxbkvzaji+iru3xqjp05amvq1r1q2k+qrarurhmjzah/ha0evixazkve2xk1zu9u/jpyf1xrwbkxqpzebvqwma+hyyaazemr6qx9uz9be==;",
  "expires": 4035948,
  "userid": "John Smith"
}

WebRedirectAuthenticator.Completed 事件处理程序会读取来自 resourcetoken API 的响应,并提取资源令牌和用户 ID。资源令牌随后作为参数传递给 DocumentClient 构造函数,该构造函数封装了用于访问 Azure Cosmos DB 的终结点、凭据和连接策略,并用于配置和执行针对 Azure Cosmos DB 的请求。 资源令牌随每个直接访问资源的请求一起发送,表明已授予对经过身份验证的用户分区集合的读取/写入访问权限。

检索文档

通过创建一个包含用户 ID 作为分区键的文档查询,可以检索只属于已通过身份验证的用户的文档,下面的代码示例对此进行了演示:

var query = client.CreateDocumentQuery<TodoItem>(collectionLink,
                        new FeedOptions
                        {
                          MaxItemCount = -1,
                          PartitionKey = new PartitionKey(UserId)
                        })
          .Where(item => !item.Id.Contains("permission"))
          .AsDocumentQuery();
while (query.HasMoreResults)
{
  Items.AddRange(await query.ExecuteNextAsync<TodoItem>());
}

查询以异步方式从指定的集合中检索属于已通过身份验证的用户的所有文档,并将它们放入 List<TodoItem> 集合中显示。

CreateDocumentQuery<T> 方法指定一个 Uri 参数(该参数表示应该在其中查询文档的集合)以及一个 FeedOptions 对象。 FeedOptions 对象指定查询可以返回无限数量的项,并指定用户 ID 作为分区键。 这样可以确保结果中只返回用户分区集合中的文档。

注意

请注意,由资源令牌代理创建的权限文档与 Xamarin.Forms 应用程序创建的文档存储在同一个文档集合中。 因此,文档查询包含一个 Where 子句,该子句针对文档集合对查询应用筛选谓词。 该子句确保权限文档不会从文档集合中返回。

有关从文档集合检索文档的详细信息,请参阅检索文档集合文档

插入文档

如以下代码示例所示,在将文档插入文档集合之前,应使用用作分区键的值更新 TodoItem.UserId 属性:

item.UserId = UserId;
await client.CreateDocumentAsync(collectionLink, item);

这可确保将文档插入到用户的分区集合中。

有关将文档插入文档集合的详细信息,请参阅将文档插入文档集合

删除文档

从分区集合中删除文档时,必须指定分区键值,如以下代码示例所示:

await client.DeleteDocumentAsync(UriFactory.CreateDocumentUri(Constants.DatabaseName, Constants.CollectionName, id),
                 new RequestOptions
                 {
                   PartitionKey = new PartitionKey(UserId)
                 });

这可确保 Azure Cosmos DB 知道要从中删除文档的分区集合。

有关从文档集合中删除文档的详细信息,请参阅从文档集合中删除文档

总结

本文介绍了如何将访问控制与分区集合相结合,使用户只能在 Xamarin.Forms 应用程序中访问自己的文档数据库文档。 将用户的标识指定为分区键可确保分区集合只能存储该用户的文档。