MAzure Active Directory を使用して Azure AI 検索結果をトリミングするためのセキュリティ フィルター

この記事では、Azure AI Search のフィルターと共にセキュリティ ID を使用して、ユーザー グループ メンバーシップに基づいて検索結果をトリミングする方法について説明します。

この記事に含まれるタスクは次のとおりです。

  • グループとユーザーを作成する
  • 作成したユーザーとグループを関連付けます
  • 新しいグループをキャッシュします
  • 関連付けられているグループでドキュメントのインデックスを作成します
  • グループ識別子フィルターでの検索要求を発行します

前提条件

Azure AI Search のインデックスには、ドキュメントに対する読み取りアクセス権を持つグループ ID のリストを格納するのにセキュリティ フィールドが必要です。 このユース ケースでは、セキュリティ保護可能な項目 (個々の大学アプリケーションなど) とその項目へのアクセス権を持っているユーザー (入学担当者) を指定するセキュリティ フィールドの間に、一対一の対応があることを前提としています。

ユーザー、グループ、および関連付けを作成するには、テナント管理者のアクセス許可 (所有者または管理者) が必要です。

次の手順で説明するように、アプリケーションをマルチテナント アプリとして登録する必要もあります。

Azure Active Directory にアプリケーションを登録する

この手順では、ユーザー アカウントとグループ アカウントのサインインを受け入れるために、アプリケーションを Azure Active Directory と統合します。 組織のテナント管理者以外が次の手順を行うには、新しいテナントの作成が必要となる場合があります。

  1. Azure ポータルで、Azure Active Directory テナントを見つけます。

  2. 左の [管理] で、[アプリの登録] を選択し、次に [新しい登録] を選択します。

  3. 登録に名前、おそらく検索アプリケーション名に似た名前を付けます。 その他の省略可能なプロパティについては、この記事を参照してください。

  4. [登録] を選択します。

  5. アプリの登録が作成されたら、アプリケーション (クライアント) ID をコピーします。 この文字列をアプリケーションに指定する必要があります。

    DotNetHowToSecurityTrimming のステップを実行している場合は、この値を app.config ファイルに貼り付けてください。

  6. [ディレクトリ (テナント) ID] をコピーします。

  7. 左側で、[API のアクセス許可] を選択してから、[アクセス許可の追加] を選択します。

  8. [Microsoft Graph] を選択し、[委任されたアクセス許可] を選択します。

  9. 次の委任されたアクセス許可を検索し、追加します。

    • Directory.ReadWrite.All
    • Group.ReadWrite.All
    • User.ReadWrite.All

    Microsoft Graph には、REST API を介して Azure Active Directory にプログラムでアクセスできる API が用意されています。 このチュートリアルのコード サンプルでは、Microsoft Graph API を呼び出すアクセス許可を使って、グループ、ユーザー、および関連付けを作成します。 この API は、高速のフィルター処理用にグループ識別子をキャッシュするためにも使われます。

  10. [テナントに管理者の同意を与えます] を選択し、同意プロセスを完了します。

ユーザーとグループを作成する

確立されたアプリケーションに検索を追加する場合は、Azure Active Directory に既存のユーザー識別子とグループ識別子がある可能性があります。 その場合は、次の 3 つのステップをスキップできます。

ただし、既存のユーザーがいない場合は、Microsoft Graph API を使ってセキュリティ プリンシパルを作成できます。 次のコード スニペットは、識別子を生成する方法を示しています。識別子は、Azure AI Search インデックスのセキュリティ フィールドのデータ値になります。 この架空の大学入学アプリケーションでは、これは入学スタッフのセキュリティ識別子です。

特に大規模な組織では、ユーザーとグループ メンバーシップが頻繁に変更される場合があります。 ユーザーとグループの ID を作成するコードは、組織のメンバーシップの変更を反映するのに十分な頻度で実行する必要があります。 さらに、Azure AI Search インデックスでは、許可されているユーザーとリソースの現在の状態を反映するために、同様の更新スケジュールが必要です。

手順 1: グループを作成する

private static Dictionary<Group, List<User>> CreateGroupsWithUsers(string tenant)
{
    Group group = new Group()
    {
        DisplayName = "My First Prog Group",
        SecurityEnabled = true,
        MailEnabled = false,
        MailNickname = "group1"
    };

手順 2: ユーザーを作成する

User user1 = new User()
{
    GivenName = "First User",
    Surname = "User1",
    MailNickname = "User1",
    DisplayName = "First User",
    UserPrincipalName = String.Format("user1@{0}", tenant),
    PasswordProfile = new PasswordProfile() { Password = "********" },
    AccountEnabled = true
};

ステップ 3: ユーザーとグループを関連付ける

List<User> users = new List<User>() { user1, user2 };
Dictionary<Group, List<User>> groups = new Dictionary<Group, List<User>>() { { group, users } };

ステップ 4: グループ識別子をキャッシュする

必要に応じて、ネットワーク待機時間を短縮するために、ユーザー グループの関連付けをキャッシュして、検索要求が発行されたときにグループがキャッシュから返されるようにし、ラウンドトリップを保存できます。 Batch API を使用して、複数のユーザーを含む 1 つの Http 要求を送信し、キャッシュを構築できます。

Microsoft Graph は、大量の要求を処理できるように設計されています。 ただし、処理できないほど多数の要求が発生すると、Microsoft Graph は HTTP 状態コード 429 で要求を失敗させます。 詳しくは、「Microsoft Graph 調整ガイド」をご覧ください。

許可されているグループでドキュメントにインデックスを付ける

Azure AI Search のクエリ操作は、Azure AI Search インデックスにより実行されます。 このステップでは、インデックス操作が、セキュリティ フィルターとして使われる識別子などの検索可能なデータを、インデックスにインポートします。

Azure AI Search は、ユーザー ID を認証したり、ユーザーが表示権限を持つコンテンツを確立するためのロジックを提供したりしません。 セキュリティによるトリミングのユース ケースでは、機密性の高いドキュメントとそのドキュメントにアクセスできるグループ識別子の関連付けをユーザーが提供し、そのまま検索インデックスにインポートされるものと想定されています。

この仮定の例では、Azure AI Search インデックスに対する PUT 要求の本文には、志願者の大学の小論文または成績証明書と、そのコンテンツを表示する権限を持つグループ識別子が含まれます。

このチュートリアルのコード サンプルで使われている汎用的な例のインデックス操作は次のようなものです。

private static void IndexDocuments(string indexName, List<string> groups)
{
    IndexDocumentsBatch<SecuredFiles> batch = IndexDocumentsBatch.Create(
        IndexDocumentsAction.Upload(
            new SecuredFiles()
            {
                FileId = "1",
                Name = "secured_file_a",
                GroupIds = new[] { groups[0] }
            }),
              ...
            };

IndexDocumentsResult result = searchClient.IndexDocuments(batch);

検索要求を発行する

セキュリティによるトリミングが目的の場合、インデックスのセキュリティ フィールドの値は、検索結果にドキュメントを含めるか除外するために使われる静的な値です。 たとえば、入学者選考のグループ識別子が "A11B22C33D44-E55F66G77-H88I99JKK" の場合、セキュリティ フィールドにその識別子を持つ Azure AI Search インデックス内のすべてのドキュメントが、呼び出し元に返される検索結果に含まれたり除外されたりします。

要求を発行したユーザーのグループに基づいて検索結果で返されるドキュメントをフィルター処理するには、次の手順のようにします。

ステップ 1: ユーザーのグループ識別子を取得する

ユーザーのグループがまだキャッシュされていない場合、またはキャッシュの有効期限を過ぎている場合は、groups 要求を発行します。

private static async void RefreshCache(IEnumerable<User> users)
{
    HttpClient client = new HttpClient();
    var userGroups = await _microsoftGraphHelper.GetGroupsForUsers(client, users);
    _groupsCache = new ConcurrentDictionary<string, List<string>>(userGroups);
}

ステップ 2: 検索要求を作成する

ユーザーのグループ メンバーシップがある場合は、適切なフィルター値を指定して検索要求を発行できます。

private static void SearchQueryWithFilter(string user)
{
    // Using the filter below, the search result will contain all documents that their GroupIds field   
    // contain any one of the Ids in the groups list
    string filter = String.Format("groupIds/any(p:search.in(p, '{0}'))", string.Join(",", String.Join(",", _groupsCache[user])));
    SearchOptions searchOptions =
        new SearchOptions()
        {
            Filter = filter
        };
    searchOptions.Select.Add("name");

    SearchResults<SecuredFiles> results = searchClient.Search<SecuredFiles>("*", searchOptions);

    Console.WriteLine("Results for groups '{0}' : {1}", _groupsCache[user], results.GetResults().Select(r => r.Document.Name));
}

ステップ 3: 結果を処理する

応答には、ユーザーが表示アクセス許可を持つドキュメントだけにフィルター処理されたリストが含まれます。 検索結果ページの作成方法によっては、フィルター処理された結果セットを反映するための視覚的な合図を含めることができます。

重要なポイント

このチュートリアルでは、ユーザー サインインを使用して Azure AI 検索結果のドキュメントをフィルター処理し、要求で指定されたフィルターと一致しないドキュメントの結果をトリミングするパターンについて説明しました。