亚马逊AWS官方博客
使用 Json Web Token (JWT) 对 Open Distro for Elasticsearch 和 Kibana 进行身份验证
基于令牌的身份验证系统在 Web 服务领域备受青睐。它们具有许多优势,包括(但不限于)安全性、可伸缩性、无状态性和可扩展性。借助 Amazon 的 Open Distro for Elasticsearch,用户现在可利用安全插件中包含的许多安全功能。其中一项功能是使用 JSON Web Token (JWT) 对用户进行身份验证,以提供单点登录体验。在本博文中,我将介绍如何生成有效的 JWT,配置安全插件以支持 JWT,并最终使用令牌中显示的申请信息对 Elasticsearch 和 Kibana 的请求进行身份验证。
先决条件
要完成此示例,请克隆或下载我们的社区存储库。 jwt-tokens
目录包含示例代码和配置,便于您跟随本博文进行学习。配置文件有两种 – kibana.yml
和 config.yml
,后者即为 docker-compose.yml
,以及一个带有 Java 代码和用来构建它的 .pom
的 token-gen
目录。
生成 JWT
JWT 由三个 Base64 编码的部分组成:一个标头、一个有效负载和一个与句点 (.
) 连接的签名。在理想情况下,JWT 是在身份验证服务器验证用户提供的凭证后由此服务器提供的。用户发出的每个请求都包含此令牌,并且 Web 服务将根据令牌中显示的申请信息允许或拒绝该请求。在本博文中,您将使用共享密码(由 HS256 算法提供)生成一个此类令牌。
让我们先分析用于生成 JWT 的一些 Java 代码示例,该代码在 16 分钟内有效。此代码使用 jjwt library 生成令牌和签名密钥:
1 import io.jsonwebtoken.Jwts;
2 import io.jsonwebtoken.SignatureAlgorithm;
3 import io.jsonwebtoken.security.Keys;
4 import java.security.Key;
5 import java.util.Date;
6 import java.util.HashMap;
7 import io.jsonwebtoken.io.Encoders;
8
9 public class JWTTest {
10 public static void main(String[] args) {
11 Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
12 Date exp = new Date(System.currentTimeMillis() + 1000000);
13 HashMap<String,Object> hm = new HashMap<>();
14 hm.put("roles","admin");
15 String jws = Jwts.builder()
16 .setClaims(hm)
17 .setIssuer("https://localhost")
18 .setSubject("admin")
19 .setExpiration(exp)
20 .signWith(key).compact();
21 System.out.println("Token:");
22 System.out.println(jws);
23 if(Jwts.parser().setSigningKey(key).parseClaimsJws(jws).getBody().getSubject().equals("test")) {
24 System.out.println("test");
25 }
26 String encoded = Encoders.BASE64.encode(key.getEncoded());
27 System.out.println("Shared secret:");
28 System.out.println(encoded);
29 }
30 }
- 第 11 行为我们提供了基于 HMAC_SHA256 算法的随机签名密钥。这是在验证 JWT 令牌时安全插件使用的签名密钥。由于我们使用的是对称密钥算法,此签名密钥是 Base64 编码的共享密钥(第 28 行)。如果我们使用的是 RSA 或 ECDSA 等非对称算法,则签名密钥将为公有密钥。第 19 行用于设置申请。安全插件会自动识别算法。
- 第 13-14 行创建了一个将密钥
roles
映射到值admin
的申请。 - 第 12 行生成了距当前时间 16 分钟的
Date
。第 19 行在Jwts.Builder
中使用此日期。 - 第 20 行使用第 11 行创建的
signing_key
签署 JWT 令牌。
您需要使用 Apache Maven 来编译和运行示例代码。我使用 Homebrew 通过以下命令安装 Maven 3.6.1
$ brew install maven
。
从 token-gen 目录中,构建并运行以下代码:
$ cd token-gen
$ mvn clean install
$ java -jar target/jwt-test-tokens-1.0-SNAPSHOT-jar-with-dependencies.jar
Token:
eyJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6ImFkbWluIiwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3QiLCJzdWIiOiJhZG1pbiIsImV4cCI6MTU1NDUyMjQzMH0.fnkQdBKqgOD-Tdf5Pv09NCiz0WlL-KFPU39CEXAMPLE
Shared Secret:
usuxqaUmbbe0VqN+Q90KCk5sXHCfEVookMRyEXAMPLE=
请务必复制此令牌和共享密钥,在一分钟后,您会用到此信息。您还可以在 token-gen
目录中的 README
中找到这些命令。
配置安全插件以使用 JWT
Open Distro for Elasticsearch 的安全插件包含一个配置文件,该配置文件可指定身份验证类型、质询以及 JWT 有效负载中必须存在的其他各种配置密钥,以便对请求进行身份验证。如要进一步验证请求,您还可以指定身份验证后端。当您启动容器时,您将使用 jwt-tokens
目录中名为 config.yml
的文件覆盖默认配置。
在您常用的编辑器中打开 jwt-tokens/config.yml
并将其更改为如下所示:
... 1 jwt_auth_domain: 2 enabled: true 3 http_enabled: true 4 transport_enabled: true 5 order: 0 6 http_authenticator: 7 type: jwt 8 challenge: false 9 config: 10 signing_key: "usuxqaUmbbe0VqN+Q90KCk5sXHCfEVookMRyEXAMPLE=" 11 jwt_header: "Authorization" 12 jwt_url_parameters: null 13 roles_key: "roles" 14 subject_key: "sub" 15 authentication_backend: 16 type: noop ...
- 第 2 行使此域能够使用 JWT 进行身份验证。
- 第 7 行选择 JWT 作为身份验证类型。
- 第 8 行将密钥质询设置为“false”。 此处不需要质询,因为 JWT 令牌包含我们需要验证的所有内容。第 15 行由于相同的原因被设置为
noop
。 - 第 10 行将 signing_key 设置为我们在上述 Java 代码中生成的 Base64 编码的共享密钥。注意:确保使用您在上一部分中生成的密钥替换此密钥。
- 第 11 行是传输令牌的 HTTP 标头。您将使用 authorization 标头和 bearer 方案。系统会默认使用“授权”标头,但您也可以使用 URL 参数传递 JWT。
- 第 13 行用于指定将用户角色存储为以逗号分隔的列表的密钥。在本例中,我们仅指定管理员。
- 第 14 行用于指定存储用户名的密钥。如果缺少此密钥,我们只会获得注册的主体申请。在上述代码中,我们只设置主体申请信息。
如果您是 Docker 和 Open Distro for Elasticsearch 的新用户,我强烈建议您从 Open Distro for Elasticsearch 文档开始入门。在本教程中,我使用的集群包含一个 Elasticsearch 节点和一个 Kibana 节点。
Kibana 改动
要模拟当我们使用标准令牌提供程序时 Kibana 的工作方式,您只需在 kibana.yml 中再添加一行。编辑 jwt-tokens/kibana.yml
并添加:
opendistro_security.auth.type: "jwt"
运行 Elasticsearch 和 Kibana
您需要一个正在运行的 Docker 环境才能继续。我使用的是 Docker Desktop for Mac。您可以在有关如何下载和配置 Docker Desktop 的博文中找到其设置和运行说明(使用来自 jwt-tokens
目录的 docker-compose.yml
而不是此博文中的配置文件)。从 jwt-tokens
目录运行以下命令:
$ docker-compose up
下载图像并启动集群后,在新终端中运行 docker ps
。您应看到与以下输出类似的内容:两个容器,其中一个运行 Elasticsearch 映像,另一个运行 Kibana 映像。
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
63c3e9df19ac amazon/opendistro-for-elasticsearch-kibana:0.9.0 "/usr/local/bin/kiba…" 8 seconds ago Up 7 seconds 0.0.0.0:5601→5601/tcp odfe-kibana
0aa5316ffbc7 amazon/opendistro-for-elasticsearch:0.9.0 "/usr/local/bin/dock…" 8 seconds ago Up 7 seconds 0.0.0.0:9200→9200/tcp, 0.0.0.0:9600→9600/tcp, 9300/tcp odfe-node1
重新初始化安全索引 [可选]
如果您仅在编辑了 config.yml
后运行 docker-compose
,可以跳过此部分。如果您在编辑 config.yml
之前的任何时候运行了 docker-compose
,则需要重新初始化安全索引并确保请求正在进行身份验证。
首先,找到带有 docker ps
的 Elasticsearch 容器:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
533f03ee0fdc amazon/opendistro-for-elasticsearch:0.9.0 "/usr/local/bin/dock…" 2 days ago Up 20 seconds 0.0.0.0:9200→9200/tcp, 0.0.0.0:9600→9600/tcp, 9300/tcp odfe-node1
3a2c4a582165 amazon/opendistro-for-elasticsearch-kibana:0.9.0 "/usr/local/bin/kiba…" 2 days ago Up 20 seconds 0.0.0.0:5601→5601/tcp odfe-kiban
复制 amazon/opendistro-for-elasticsearch
容器的 CONTAINER ID
。在我讲的示例中,容器 ID 为 533f03ee0fdc
。您可以通过运行以下命令授权 Bash 访问该容器:
$ docker exec -it 533f03ee0fdc /bin/bash
请务必在上述命令中使用您的容器 ID。重新初始化安全索引并退出:
$ plugins/opendistro_security/tools/securityadmin.sh -f plugins/opendistro_security/securityconfig/config.yml -icl -nhnv -cert config/kirk.pem -cacert config/root-ca.pem -key config/kirk-key.pem -t config Open Distro Security Admin v6 Will connect to localhost:9300 ... done Elasticsearch Version: 6.5.4 Open Distro Security Version: 0.9.0.0 Connected as CN=kirk,OU=client,O=client,L=test,C=de Contacting elasticsearch cluster 'elasticsearch' and wait for YELLOW clusterstate ... Clustername: odfe-cluster Clusterstate: YELLOW Number of nodes: 1 Number of data nodes: 1 .opendistro_security index already exists, so we do not need to create one. Populate config from /usr/share/elasticsearch Will update 'security/config' with plugins/opendistro_security/securityconfig/config.yml SUCC: Configuration for 'config' created or updated Done with success $ exit
测试您的更改
现在,您可以通过添加 authorization 标头来测试一些基本命令。请务必使用上面生成的令牌替换以下命令中的令牌:
$ curl -XGET https://localhost:9200/_cat/nodes -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6ImFkbWluIiwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3QiLCJzdWIiOiJhZG1pbiIsImV4cCI6MTU1NDc1Nzk3M30.KY5gC4yrBXXYYcaEJOl-xyiEr98h9Sw9dIWwEXAMPLE" --insecure 172.21.0.3 37 38 3 0.03 0.11 0.09 mdi * WTNYA_5
$ curl -XGET https://localhost:9200/_cluster/health\?pretty -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6ImFkbWluIiwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3QiLCJzdWIiOiJhZG1pbiIsImV4cCI6MTU1NDc1Nzk3M30.KY5gC4yrBXXYYcaEJOl-xyiEr98h9Sw9dIWwlzJYpBg" --insecure { "cluster_name" : "odfe-cluster", "status" : "yellow", "timed_out" : false, "number_of_nodes" : 1, "number_of_data_nodes" : 1, "active_primary_shards" : 7, "active_shards" : 7, "relocating_shards" : 0, "initializing_shards" : 0, "unassigned_shards" : 5, "delayed_unassigned_shards" : 0, "number_of_pending_tasks" : 0, "number_of_in_flight_fetch" : 0, "task_max_waiting_in_queue_millis" : 0, "active_shards_percent_as_number" : 58.333333333333336 }
$ curl -XGET https://localhost:9200/_opendistro/_security/authinfo\?pretty -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6ImFkbWluIiwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3QiLCJzdWIiOiJhZG1pbiIsImV4cCI6MTU1NDc1Nzk3M30.KY5gC4yrBXXYYcaEJOl-xyiEr98h9Sw9dIWwlzJYpBg
" --insecure { "user" : "User [name=admin, roles=[admin], requestedTenant=null]", "user_name" : "admin", "user_requested_tenant" : null, "remote_address" : "172.23.0.1:53104", "backend_roles" : [ "admin" ], "custom_attribute_names" : [ "attr.jwt.iss", "attr.jwt.sub", "attr.jwt.exp", "attr.jwt.roles" ], "roles" : [ "all_access", "own_index" ], "tenants" : { "admin_tenant" : true, "admin" : true }, "principal" : null, "peer_certificates" : "0", "sso_logout_url" : null }
然后,您可以从终端向 Kibana 发出请求,并记下成功的响应:
$ curl -XGET http://localhost:5601 -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6ImFkbWluIiwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3QiLCJzdWIiOiJhZG1pbiIsImV4cCI6MTU1MzY0Mjc1NX0.2RVy0VEObwduF9nNZas498LTJMRLC9luTuebEXAMPLE" -i
HTTP/1.1 302 Found location: /app/kibana kbn-name: kibana set-cookie: security_storage=Fe26.2**a86a495463a9ed2aef99e9499025b000888bc70232d006765c9990f8c9d7412*viOmkphhLLIDeBTxX9_OkQ*lIBpboN6gQ07QvwY7mMp-48IsrvI0qtfaRR8_VmPesYmlqlNizId2smn-kXtIJdsmZBpz7y4WLJzmqP0hKKCBAAJ9Bccj-fVh5QJdHW6mWEhuS870VlB9PUMZAnQ8ju6D8Gs-70A16rodBDSI4b601EhJET4vtMObTFmvYkiavqKvc9CPbwMpHRQdIKwX9AzSjbekMC8CSn1PgzMbtNijYNFd3sLZHrDxrqTSQijm8M**ba624f98f91081024b49264a08c692287b30bca4f185aa8925c1bb238cdf27ef*fc9z6yinUj2Xp920Iy-GoKdVzO5G4aZRsxQWi_bVH-Y; Path=/ set-cookie: security_preferences=Fe26.2**a2791807692cd418aa644804fd0e6e5cd33421a899e0797d8a97ec4e7f2cbf0*guZ5n6zMcCwylCPOazyyew*1n43XcDV1NcGvgl-VwD07njHLkxn-VdgQNVMk5ZQSsw**f25a10407839cc2869b06826eb5459f166baf6fcea11df6b1f4a316152fec3e4*K5wr95D7cVoetpvEFjdzjSN-mgvBEU9tWpx6QiLgEuE; Max-Age=2217100485; Expires=Mon, 27 Jun 2089 17:56:50 GMT; Path=/ set-cookie: security_authentication=Fe26.2**5ca6f12884a00a406f89887bb91f33ee7a68f22c815996a9adbda934698364d*OuII1jATnWfYzaHIv4_HvQ*qoTlwVqRvpDzkWmq-JYZbXpSbEJ6DyG5qhmNenM0GB6vbGEcnkXmpUFvOICkAyRuzmKwl9Uut1GYM98TLwhTZbzFb6Z1d5Sb4MOpk6DJNFjuokIm0u9tqsCwCGMEO_avmosVy4gceAluSX-7vN-vC461jt2B3_DIbyeREjPLtjr91a2I95nGQRir_-4cypkjUaS3Blub1ZC7fNnkBcK5POvo-nKTXJmx5KQx4O_6zVc3vFfoQLJ7_AUrLAID_htMHMv5o7_qn1oMHP-LTr5zvO4iDLlY1UgBJCmikpMatxPg8ophKxWkMRuIdo4UaZEjrzXwQPJtYBmpJxwQtolJQB5jwOnNNVqtUeiI7sWitHM**1c4cf336b71a513045bf0bfe50ff96447c213f70dfd3745d713e57235a7edff9*fLp9DLSMhgKHjOIJ8VDHMbVI9Z7W56Velx4Pi5STK4s; Max-Age=3600; Expires=Tue, 26 Mar 2019 21:42:05 GMT; HttpOnly; Path=/ cache-control: no-cache content-length: 0 connection: close Date: Tue, 26 Mar 2019 20:42:05 GMT
小结
祝贺您! 您已创建 JWT 令牌,用于验证和控制对 Open Distro for Elasticsearch 集群的访问。您已修改安全插件的配置以接受 JWT。您已在容器中运行修改后的 Elasticsearch 和 Kibana,并成功发送查询。
有问题或疑问? 希望参与讨论? 您可以在我们的论坛上获得帮助并讨论 Open Distro for Elasticsearch。您可以在这里提出问题。