使用搜索应用程序与不受信任的客户端
编辑使用搜索应用程序与不受信任的客户端
编辑构建用于搜索用例的前端应用程序时,返回搜索结果主要有两种方法:
- 客户端(用户的浏览器)向应用程序后端发出 API 请求,后端再向 Elasticsearch 发出请求。Elasticsearch 集群不会暴露给最终用户。
- 客户端(用户的浏览器)直接向搜索服务发出 API 请求——在这种情况下,客户端可以访问 Elasticsearch 集群。
本指南描述了采用第二种方法时的最佳实践。具体来说,我们将解释如何将搜索应用程序与向搜索应用程序搜索 API发出直接请求的前端应用程序一起使用。
这种方法有一些优点:
- 无需维护前端应用程序和 Elasticsearch 之间的直通查询系统
- 直接向 Elasticsearch 发出请求可加快响应时间
- 查询配置在一个地方管理:您在 Elasticsearch 中的搜索应用程序配置
我们将介绍:
使用具有角色限制的 Elasticsearch API 密钥
编辑当前端应用程序可以向 Elasticsearch 发出直接 API 请求时,限制它们可以执行的操作非常重要。在本例中,前端应用程序应该只能调用搜索应用程序搜索 API。为确保这一点,我们将创建具有角色限制的 Elasticsearch API 密钥。角色限制用于指定角色生效的条件。
以下 Elasticsearch API 密钥只能通过搜索应用程序搜索 API 访问website-product-search
搜索应用程序:
resp = client.security.create_api_key( name="my-restricted-api-key", expiration="7d", role_descriptors={ "my-restricted-role-descriptor": { "indices": [ { "names": [ "website-product-search" ], "privileges": [ "read" ] } ], "restriction": { "workflows": [ "search_application_query" ] } } }, ) print(resp)
const response = await client.security.createApiKey({ name: "my-restricted-api-key", expiration: "7d", role_descriptors: { "my-restricted-role-descriptor": { indices: [ { names: ["website-product-search"], privileges: ["read"], }, ], restriction: { workflows: ["search_application_query"], }, }, }, }); console.log(response);
POST /_security/api_key { "name": "my-restricted-api-key", "expiration": "7d", "role_descriptors": { "my-restricted-role-descriptor": { "indices": [ { "names": ["website-product-search"], "privileges": ["read"] } ], "restriction": { "workflows": ["search_application_query"] } } } }
|
|
|
指定工作流限制至关重要。如果没有此限制,Elasticsearch API 密钥可以直接调用_search
并发出任意 Elasticsearch 查询。在处理不受信任的客户端时,这是不安全的。
响应将如下所示:
{ "id": "v1CCJYkBvb5Pg9T-_JgO", "name": "my-restricted-api-key", "expiration": 1689156288526, "api_key": "ztVI-1Q4RjS8qFDxAVet5w", "encoded": "djFDQ0pZa0J2YjVQZzlULV9KZ086enRWSS0xUTRSalM4cUZEeEFWZXQ1dw" }
然后可以直接在授权标头中使用编码值。这是一个使用 cURL 的示例:
curl -XPOST "https://127.0.0.1:9200/_application/search_application/website-product-search/_search" \ -H "Content-Type: application/json" \ -H "Authorization: ApiKey djFDQ0pZa0J2YjVQZzlULV9KZ086enRWSS0xUTRSalM4cUZEeEFWZXQ1dw" \ -d '{ "params": { "field_name": "color", "field_value": "red", "agg_size": 5 } }'
如果expiration
不存在,则默认情况下 Elasticsearch API 密钥永不过期。可以使用使 API 密钥无效 API使 API 密钥失效。
具有角色限制的 Elasticsearch API 密钥也可以使用字段级和文档级安全性。这进一步限制了前端应用程序查询搜索应用程序的方式。
使用搜索应用程序进行参数验证
编辑您的搜索应用程序使用搜索模板来呈现查询。模板参数传递给搜索应用程序搜索 API。对于前端应用程序或不受信任的客户端使用的 API,我们需要进行严格的参数验证。搜索应用程序定义一个 JSON 模式,该模式描述了搜索应用程序搜索 API 允许的参数。
以下示例定义了一个具有严格参数验证的搜索应用程序:
resp = client.search_application.put( name="website-product-search", search_application={ "indices": [ "website-products" ], "template": { "script": { "source": { "query": { "term": { "{{field_name}}": "{{field_value}}" } }, "aggs": { "color_facet": { "terms": { "field": "color", "size": "{{agg_size}}" } } } }, "params": { "field_name": "product_name", "field_value": "hello world", "agg_size": 5 } }, "dictionary": { "properties": { "field_name": { "type": "string", "enum": [ "name", "color", "description" ] }, "field_value": { "type": "string" }, "agg_size": { "type": "integer", "minimum": 1, "maximum": 10 } }, "required": [ "field_name" ], "additionalProperties": False } } }, ) print(resp)
const response = await client.searchApplication.put({ name: "website-product-search", search_application: { indices: ["website-products"], template: { script: { source: { query: { term: { "{{field_name}}": "{{field_value}}", }, }, aggs: { color_facet: { terms: { field: "color", size: "{{agg_size}}", }, }, }, }, params: { field_name: "product_name", field_value: "hello world", agg_size: 5, }, }, dictionary: { properties: { field_name: { type: "string", enum: ["name", "color", "description"], }, field_value: { type: "string", }, agg_size: { type: "integer", minimum: 1, maximum: 10, }, }, required: ["field_name"], additionalProperties: false, }, }, }, }); console.log(response);
PUT _application/search_application/website-product-search { "indices": [ "website-products" ], "template": { "script": { "source": { "query": { "term": { "{{field_name}}": "{{field_value}}" } }, "aggs": { "color_facet": { "terms": { "field": "color", "size": "{{agg_size}}" } } } }, "params": { "field_name": "product_name", "field_value": "hello world", "agg_size": 5 } }, "dictionary": { "properties": { "field_name": { "type": "string", "enum": ["name", "color", "description"] }, "field_value": { "type": "string" }, "agg_size": { "type": "integer", "minimum": 1, "maximum": 10 } }, "required": [ "field_name" ], "additionalProperties": false } } }
使用该定义,搜索应用程序搜索 API 执行以下参数验证:
- 它只接受
field_name
、field_value
和aggs_size
参数 -
field_name
仅限于采用“name”、“color”和“description”值 -
agg_size
定义术语聚合的大小,它只能取1
和10
之间的值
使用 CORS
编辑使用这种方法意味着用户的浏览器将直接向 Elasticsearch API 发出请求。Elasticsearch 支持跨域资源共享 (CORS),但此功能默认情况下处于禁用状态。因此,浏览器将阻止这些请求。
为此,有两种解决方法:
在 Elasticsearch 上启用 CORS
编辑这是最简单的选项。通过将以下内容添加到您的elasticsearch.yml
文件中来在 Elasticsearch 上启用 CORS:
http.cors.allow-origin: "*" # Only use unrestricted value for local development # Use a specific origin value in production, like `http.cors.allow-origin: "https://<my-website-domain.example>"` http.cors.enabled: true http.cors.allow-credentials: true http.cors.allow-methods: OPTIONS, POST http.cors.allow-headers: X-Requested-With, X-Auth-Token, Content-Type, Content-Length, Authorization, Access-Control-Allow-Headers, Accept
在 Elastic Cloud 上,您可以通过编辑您的 Elasticsearch 用户设置来执行此操作。
- 从您的部署菜单中,转到编辑页面。
- 在Elasticsearch部分,选择管理用户设置和扩展。
- 使用上面的配置更新用户设置。
- 选择保存更改。
通过支持 CORS 的服务器代理请求
编辑如果您无法在 Elasticsearch 上启用 CORS,则可以通过支持 CORS 的服务器代理请求。这比较复杂,但这是一个可行的选项。