用于在 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, ...') 而不是相等表达式,则有望获得亚秒级响应时间。

先决条件

  • 包含组或用户标识的字段必须是包含“filterable”属性的字符串。 它应该是一个集合。 它不应允许 null。

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

    {  
        "Employee-1": {  
            "id": "100-1000-10-1-10000-1",
            "name": "Abram",   
            "salary": 75000,   
            "married": true,
            "security_id": "10011"
        },
        "Employee-2": {  
            "id": "200-2000-20-2-20000-2",
            "name": "Adams",   
            "salary": 75000,   
            "married": true,
            "security_id": "20022"
        } 
    }  
    

    注意

    本文不介绍检索主体标识符并将这些字符串注入可由 Azure AI 搜索编制索引的源文档的过程。 有关如何获取标识符,请参阅标识服务提供商的文档。

创建安全字段

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

  1. 将一个安全字段添加为 Collection(Edm.String)。 确保该字段的 filterable 属性设置为 true,以便根据用户拥有的访问权限筛选搜索结果。 例如,如果针对 file_name 为“secured_file_b”的文档将 group_ids 字段设置为 ["group_id1, group_id2"],则只有属于组 ID“group_id1”或“group_id2”的用户才对该文件拥有读访问权限。

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

  2. 索引需要文档键。 “file_id”字段符合该要求。 索引还应包含可搜索的内容。 在此示例中,“file_name”和“file_description”字段符合该要求。

    POST https://[search service].search.azure.cn/indexes/securedfiles/docs/index?api-version=2023-11-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 将数据推送到索引中

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

POST https://[search service].search.azure.cn/indexes/securedfiles/docs/index?api-version=2023-11-01

在请求正文中,指定文档的内容:

{
    "value": [
        {
            "@search.action": "upload",
            "file_id": "1",
            "file_name": "secured_file_a",
            "file_description": "File access is restricted to the 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=2020-06-30
Content-Type: application/json  
api-key: [admin or query key]

在请求正文中指定筛选器:

{
   "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 函数会筛选出任何用户主体都对其没有读访问权限的搜索结果。 主体标识符可以表示安全组、角色甚至用户自己的标识等信息。

对于基于 Microsoft Entra ID 的替代模式,或者要重新访问其他安全功能,请参阅以下链接。