用于在 Azure AI 搜索中修整结果的安全筛选器

Azure AI 搜索不提供本机文档级权限,也不能根据用户权限改变同一索引中的搜索结果。 一种解决方法是创建一个筛选器,以便根据包含组或用户标识的字符串来修整搜索结果。

本文将介绍一种安全筛选模式,其中包括以下步骤:

  • 使用所需内容汇编源文档
  • 为主体标识符创建一个字段
  • 将文档推送到搜索索引以编制索引
  • 使用 search.in 筛选器函数查询索引

文章最后提供了提供实践学习的演示和示例链接。 建议先查看本文以了解模式。

关于安全筛选器模式

尽管 Azure AI 搜索未与用于访问索引中内容的安全子系统集成,但筛选器可以满足许多客户的文档级安全要求。

在 Azure AI 搜索中,安全筛选器是一个常规 OData 筛选器,它根据由安全主体组成的字符串包含或排除搜索结果。 不会通过安全主体进行身份验证或授权。 主体只是在筛选表达式中使用的一个字符串,用于在搜索结果中包含或排除文档。

有多种方法可以实现安全筛选。 一种方法是通过相等表达式进行复杂析取:例如 Id eq 'id1' or Id eq 'id2' 等。 此方法容易出错且难以维护,如果列表包含数百甚至数千个值,会将查询响应时间减慢许多秒。

更好的解决方法是将 search.in 函数用于安全筛选器,如本文中所述。 如果使用 search.in(Id, 'id1, id2, ...') 而不是相等表达式,则有望获得亚秒级响应时间。

先决条件

  • 包含组或用户标识的字符串字段,例如 Microsoft Entra 对象标识符。

  • 同一文档中的其他字段应提供该组或用户可访问的内容。 在以下 JSON 文档中,“security_id”字段包含安全筛选器中使用的标识,如果调用方的标识与文档的“security_id”匹配,则该字段包含姓名、工资和婚姻状况。

    {  
        "Employee-1": {  
            "employee_id": "100-1000-10-1-10000-1",
            "name": "Abram",   
            "salary": 75000,   
            "married": true,
            "security_id": "alphanumeric-object-id-for-employee-1"
        },
        "Employee-2": {  
            "employee_id": "200-2000-20-2-20000-2",
            "name": "Adams",   
            "salary": 75000,   
            "married": true,
            "security_id": "alphanumeric-object-id-for-employee-2"
        } 
    }  
    

创建安全字段

在搜索索引的字段集合中,需有一个包含组或用户标识的字段,类似于以上示例中虚构的“security_id”字段。

  1. 将一个安全字段添加为 Collection(Edm.String)

  2. 将字段的 filterable 属性设置为 true

  3. 将该字段的 retrievable 属性设置为 false,这样就不会将其返回为搜索请求的一部分。

  4. 索引需要文档键。 “file_id”字段符合该要求。

  5. 索引还应包含可搜索和可检索的内容。 在此示例中,“file_name”和“file_description”字段符合该要求。

    以下索引架构满足字段要求。 在 Azure AI 搜索上编制索引的文档应具有所有这些字段的值,包括“group_ids”。 对于 file_name 为“secured_file_b”的文档,只有属于组 ID“group_id1”或“group_id2”的用户才对该文件拥有读访问权限。

    POST https://[search service].search.azure.cn/indexes/securedfiles/docs/index?api-version=2024-07-01
    {
         "name": "securedfiles",  
         "fields": [
             {"name": "file_id", "type": "Edm.String", "key": true, "searchable": false },
             {"name": "file_name", "type": "Edm.String", "searchable": true },
             {"name": "file_description", "type": "Edm.String", "searchable": true },
             {"name": "group_ids", "type": "Collection(Edm.String)", "filterable": true, "retrievable": false }
         ]
     }
    

使用 REST API 将数据推送到索引中

使用文档填充搜索索引,这些文档为字段集合中的每个字段提供值,包括安全字段的值。 Azure AI 搜索不提供用于专门填充安全字段的 API 或功能。 然而,本文末尾列出的几个示例解释了填充该字段的技术。

在 Azure AI 搜索中,加载数据的方法包括:

  • 导入填充了所有字段的文档的单个推送或拉取(索引器)操作
  • 多个推送或拉取操作。 只要辅助导入操作以正确的文档标识符为目标,就可以通过多个导入单独加载字段。

以下示例展示了将单个 HTTP POST 请求发送到索引的 URL 终结点的文档集合(请参阅“文档 - 索引”)。 HTTP 请求的正文是要编制索引的文档的 JSON 呈现:

POST https://[search service].search.azure.cn/indexes/securedfiles/docs/index?api-version=2024-07-01
{
    "value": [
        {
            "@search.action": "upload",
            "file_id": "1",
            "file_name": "secured_file_a",
            "file_description": "File access is restricted to Human Resources.",
            "group_ids": ["group_id1"]
        },
        {
            "@search.action": "upload",
            "file_id": "2",
            "file_name": "secured_file_b",
            "file_description": "File access is restricted to Human Resources and Recruiting.",
            "group_ids": ["group_id1", "group_id2"]
        },
        {
            "@search.action": "upload",
            "file_id": "3",
            "file_name": "secured_file_c",
            "file_description": "File access is restricted to Operations and Logistics.",
            "group_ids": ["group_id5", "group_id6"]
        }
    ]
}

如需使用组列表更新现有文档,可以使用 mergemergeOrUpload 操作:

{
    "value": [
        {
            "@search.action": "mergeOrUpload",
            "file_id": "3",
            "group_ids": ["group_id7", "group_id8", "group_id9"]
        }
    ]
}

在查询中应用安全筛选器

若要基于 group_ids 访问权限修整文档,应发出包含 group_ids/any(g:search.in(g, 'group_id1, group_id2,...')) 筛选器的搜索查询,其中,'group_id1, group_id2,...' 是搜索请求发出者所属的组。

此筛选器匹配其 group_ids 字段包含某个给定标识符的所有文档。 有关使用 Azure AI 搜索搜索文档的完整详细信息,可以阅读搜索文档

此示例演示如何使用 POST 请求设置查询。

发出 HTTP POST 请求,并在请求正文中指定筛选器:

POST https://[service name].search.azure.cn/indexes/securedfiles/docs/search?api-version=2024-07-01

{
   "filter":"group_ids/any(g:search.in(g, 'group_id1, group_id2'))"  
}

应该获取 group_ids 包含“group_id1”或“group_id2”的文档。 换而言之,应获取请求发出者对其拥有读访问权限的文档。

{
 [
   {
    "@search.score":1.0,
     "file_id":"1",
     "file_name":"secured_file_a",
   },
   {
     "@search.score":1.0,
     "file_id":"2",
     "file_name":"secured_file_b"
   }
 ]
}

后续步骤

本文介绍了基于用户标识和 search.in() 函数筛选结果的模式。 可以使用此函数传入请求用户的主体标识符,以将其与每个目标文档关联的主体标识符进行匹配。 处理搜索请求时,search.in 函数会筛选出任何用户主体都对其没有读访问权限的搜索结果。 主体标识符可以表示安全组、角色甚至用户自己的标识等信息。

有关更多示例、演示和视频,请参阅以下资源: