欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos

本篇概览

  • 本篇是《java与es8实战》系列的第五篇,总体目标明确:实战在SpringBoot应用中操作elasticsearch8,今天的重点是SpringBoot应用连接带有安全检查的elasticsearch8服务端
  • 连接需要安全检查的elasticsearch8是为了更接近真实环境,首先,连接是基于自签证书的https协议,其次,认证方式有两种
  1. 第一种是账号密码
  2. 第二种是es服务端授权的API Key
  • 以上两种认证方式,在今天的实战中都会尝试,再加上前文《java与es8实战之四:SpringBoot应用中操作es8(无安全检查)》,可以小小的梳理一下SpringBoot应用连接es8的方式了,如下所示,直连、证书+账号密码、证书+API key等三种

  • 今天的实战总体目标可以拆解为两个子任务
  1. 在SpringBoot中连接elasticsearch8
  2. 在SpringBoot中使用elasticsearch8官方的Java API Client
  • 接下来直接开始

部署elasticsearch集群(需要安全检查)

  • 关于快速部署elasticsearch集群(需要安全检查),可以参考《docker-compose快速部署elasticsearch-8.x集群+kibana》

创建API Key

  • 除了账号密码,ES还提供了一种安全的访问方式:API Key,java应用持有es签发的API Key也能顺利发送指令到es,接下来咱们先生成API Key,再在应用中使用此API Key
  • 《docker-compose快速部署elasticsearch-8.x集群+kibana》一文中,的咱们将自签证书从容器中复制出来了,现在在证书所在目录执行以下命令,注意参数expiration代表这个ApiKey的有效期,我这里随意设置为10天
curl -X POST "https://localhost:9200/_security/api_key?pretty" \
--cacert es01.crt \
-u elastic:123456 \
-H 'Content-Type: application/json' \
-d'
{"name": "my-api-key-10d","expiration": "10d"
}
'
  • 会收到以下响应,其中的encoded字段就是API Key
{"id" : "eUV1V4EBucGIxpberGuJ","name" : "my-api-key-10d","expiration" : 1655893738633,"api_key" : "YyhSTh9ETz2LKBk3-Iy2ew","encoded" : "ZVVWMVY0RUJ1Y0dJeHBiZXJHdUo6WXloU1RoOUVUejJMS0JrMy1JeTJldw=="
}

Java应用连接elasticsearch的核心套路

  • 不论是直连,还是带安全检查的连接,亦或是与SpringBoot的集成使之更方便易用,都紧紧围绕着一个不变的核心套路,该套路由两部分组成,掌握了它们就能在各种条件下成功连接es
  1. 首先,是builder pattern,连接es有关的代码,各种对象都是其builder对象的build方法创建的,建议您提前阅读《java与es8实战之一》一文,看完后,满屏的builder代码可以从丑变成美…
  2. 其次,就是java应用能向es发请求的关键:ElasticsearchClient对象,该对象的创建是有套路的,如下图,先创建RestClient,再基于RestClient创建ElasticsearchTransport,最后基于ElasticsearchTransport创建ElasticsearchClient,这是个固定的套路,咱们后面的操作都是基于此的,可能会加一点东西,但不会改变流程和图中的对象
  • 准备完毕,开始写代码

新建子工程

  • 为了便于管理依赖库版本和源码,《java与es8实战》系列的所有代码都以子工程的形式存放在父工程elasticsearch-tutorials中

  • 《java与es8实战之二:实战前的准备工作》一文说明了创建父工程的详细过程

  • 在父工程elasticsearch-tutorials中新建名为crud-with-security的子工程,其pom.xml内容如下

<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><!-- 请改为自己项目的parent坐标 --><parent><artifactId>elasticsearch-tutorials</artifactId><groupId>com.bolingcavalry</groupId><version>1.0-SNAPSHOT</version><relativePath>../pom.xml</relativePath></parent><modelVersion>4.0.0</modelVersion><groupId>com.bolingcavalry</groupId><!-- 请改为自己项目的artifactId --><artifactId>crud-with-security</artifactId><packaging>jar</packaging><!-- 请改为自己项目的name --><name>crud-with-security</name><version>1.0-SNAPSHOT</version><url>https://github.com/zq2599</url><!--不用spring-boot-starter-parent作为parent时的配置--><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>${springboot.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><!-- 不加这个,configuration类中,IDEA总会添加一些提示 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><!-- exclude junit 4 --><exclusions><exclusion><groupId>junit</groupId><artifactId>junit</artifactId></exclusion></exclusions></dependency><!-- junit 5 --><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><scope>test</scope></dependency><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-engine</artifactId><scope>test</scope></dependency><!-- elasticsearch引入依赖  start --><dependency><groupId>co.elastic.clients</groupId><artifactId>elasticsearch-java</artifactId></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId></dependency><!-- 使用spring boot Maven插件时需要添加该依赖 --><dependency><groupId>jakarta.json</groupId><artifactId>jakarta.json-api</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency></dependencies><build><plugins><!-- 需要此插件,在执行mvn test命令时才会执行单元测试 --><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>3.0.0-M4</version><configuration><skipTests>false</skipTests></configuration></plugin><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins><resources><resource><directory>src/main/resources</directory><includes><include>**/*.*</include></includes></resource></resources></build>
</project>

配置文件

  • 为了成功连接es,需要两个配置文件:SpringBoot常规的配置application.yml和es的自签证书
  • 首先是application.yml,如下所示,因为本篇要验证两种授权方式,所以账号、密码、apiKey全部填写在配置文件中,如下所示
elasticsearch:username: elasticpasswd: 123456apikey: ZVVWMVY0RUJ1Y0dJeHBiZXJHdUo6WXloU1RoOUVUejJMS0JrMy1JeTJldw==# 多个IP逗号隔开hosts: 127.0.0.1:9200
  • 接下来是es的自签证书,这是SpringBoot应用在向es8发起https请求时需要用到的,在《docker-compose快速部署elasticsearch-8.x集群+kibana》一文中已经将其成功从容器中复制出来,现在请将其放在application.yml文件所在位置,如下图

编码:启动类

  • SpringBoot启动类,平淡无奇的那种
@SpringBootApplication
public class SecurityApplication {public static void main(String[] args) {SpringApplication.run(SecurityApplication.class, args);}
}

编码:配置文件

  • 接下来是全文的重点:通过Config类向Spring环境注册服务bean,这里有这两处要注意的地方

  • 第一个要注意的地方:向Spring环境注册的服务bean一共有两个,它们都是ElasticsearchClient类型,一个基于账号密码认证,另一个基于apiKey认证

  • 第二个要注意的地方:SpringBoot向es服务端发起的是https请求,这就要求在建立连接的时候使用正确的证书,也就是刚才咱们从容器中复制出来再放入application.yml所在目录的es01.crt文件,使用证书的操作发生在创建ElasticsearchTransport对象的时候,属于前面总结的套路步骤中的一步,如下图红框所示

  • 配置类的详细代码如下,有几处需要注意的地方稍后会说明
package com.bolingcavalry.security.config;import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.apache.http.message.BasicHeader;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.SSLContexts;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestClientBuilder.HttpClientConfigCallback;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.StringUtils;import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;@ConfigurationProperties(prefix = "elasticsearch") //配置的前缀
@Configuration
@Slf4j
public class ClientConfig {@Setterprivate String hosts;@Setterprivate String username;@Setterprivate String passwd;@Setterprivate String apikey;/*** 解析配置的字符串,转为HttpHost对象数组* @return*/private HttpHost[] toHttpHost() {if (!StringUtils.hasLength(hosts)) {throw new RuntimeException("invalid elasticsearch configuration");}String[] hostArray = hosts.split(",");HttpHost[] httpHosts = new HttpHost[hostArray.length];HttpHost httpHost;for (int i = 0; i < hostArray.length; i++) {String[] strings = hostArray[i].split(":");httpHost = new HttpHost(strings[0], Integer.parseInt(strings[1]), "https");httpHosts[i] = httpHost;}return httpHosts;}@Beanpublic ElasticsearchClient clientByPasswd() throws Exception {ElasticsearchTransport transport = getElasticsearchTransport(username, passwd, toHttpHost());return new ElasticsearchClient(transport);}private static SSLContext buildSSLContext() {ClassPathResource resource = new ClassPathResource("es01.crt");SSLContext sslContext = null;try {CertificateFactory factory = CertificateFactory.getInstance("X.509");Certificate trustedCa;try (InputStream is = resource.getInputStream()) {trustedCa = factory.generateCertificate(is);}KeyStore trustStore = KeyStore.getInstance("pkcs12");trustStore.load(null, null);trustStore.setCertificateEntry("ca", trustedCa);SSLContextBuilder sslContextBuilder = SSLContexts.custom().loadTrustMaterial(trustStore, null);sslContext = sslContextBuilder.build();} catch (CertificateException | IOException | KeyStoreException | NoSuchAlgorithmException |KeyManagementException e) {log.error("ES连接认证失败", e);}return sslContext;}private static ElasticsearchTransport getElasticsearchTransport(String username, String passwd, HttpHost...hosts) {// 账号密码的配置final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, passwd));// 自签证书的设置,并且还包含了账号密码HttpClientConfigCallback callback = httpAsyncClientBuilder -> httpAsyncClientBuilder.setSSLContext(buildSSLContext()).setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE).setDefaultCredentialsProvider(credentialsProvider);// 用builder创建RestClient对象RestClient client = RestClient.builder(hosts).setHttpClientConfigCallback(callback).build();return new RestClientTransport(client, new JacksonJsonpMapper());}private static ElasticsearchTransport getElasticsearchTransport(String apiKey, HttpHost...hosts) {// 将ApiKey放入header中Header[] headers = new Header[] {new BasicHeader("Authorization", "ApiKey " + apiKey)};// es自签证书的设置HttpClientConfigCallback callback = httpAsyncClientBuilder -> httpAsyncClientBuilder.setSSLContext(buildSSLContext()).setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE);// 用builder创建RestClient对象RestClient client = RestClient.builder(hosts).setHttpClientConfigCallback(callback).setDefaultHeaders(headers).build();return new RestClientTransport(client, new JacksonJsonpMapper());}@Beanpublic ElasticsearchClient clientByApiKey() throws Exception {ElasticsearchTransport transport = getElasticsearchTransport(apikey, toHttpHost());return new ElasticsearchClient(transport);}
}
  • 上述代码有以下几处需要注意
  1. 这个配置类为业务代码提供了两个服务bean,作用是操作es,这两个服务bean分别由clientByPasswd和clientByApiKey两个方法负责提供
  2. 名为getElasticsearchTransport的方法有两个,分别负责配置两种鉴权方式:账号密码和apiKey
  3. 设置证书的操作被封装在buildSSLContext方法中,在创建ElasticsearchTransport对象的时候会用到

编码:业务类

  • 既然两个ElasticsearchClient对象都已经注册到Spring环境,那么只要在业务类中注入就能用来操作es了

  • 新建业务类ESService.java,如下,可见通过Resource注解选择了账号密码鉴权的ElasticsearchClient对象

package com.bolingcavalry.security.service;import co.elastic.clients.elasticsearch.ElasticsearchClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import java.io.IOException;@Service
public class ESService {@Resource(name="clientByPasswd")private ElasticsearchClient elasticsearchClient;public void addIndex(String name) throws IOException {elasticsearchClient.indices().create(c -> c.index(name));}public boolean indexExists(String name) throws IOException {return elasticsearchClient.indices().exists(b -> b.index(name)).value();}public void delIndex(String name) throws IOException {elasticsearchClient.indices().delete(c -> c.index(name));}
}
  • 至此,基本功能算是开发完成了,接下来编写单元测试代码,验证能否成功操作es8

编码:单元测试

  • 新增单元测试类ESServiceTest.java,如下,功能是调用业务类ESService执行创建、删除、查找等索引操作
package com.bolingcavalry.security.service;import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
class ESServiceTest {@AutowiredESService esService;@Testvoid addIndex() throws Exception {String indexName = "test_index";Assertions.assertFalse(esService.indexExists(indexName));esService.addIndex(indexName);Assertions.assertTrue(esService.indexExists(indexName));esService.delIndex(indexName);Assertions.assertFalse(esService.indexExists(indexName));}
}
  • 编码完成,开始验证

验证:账号密码鉴权

  • 现在ESService中使用的es服务类是账号密码鉴权的,运行单元测试,看看是否可以成功操作ES,如下图,符合预期

验证:ApiKey鉴权

  • 再来试试ApiKey鉴权操作es,修改ESService.java源码,改动如下图红框所示

  • 为了检查创建的索引是否符合预期,注释掉单元测试类中删除索引的代码,如下图,如此一来,单元测试执行完成后,新增的索引还保留在es环境中

  • 再执行一次单元测试,依旧符合预期

  • 用eshead查看,可见索引创建成功

  • 至此,SpringBoot操作带有安全检查的elasticsearch8的实战就完成了,在SpringData提供elasticsearch8操作的库之前,基于es官方原生client库的操作是常见的elasticsearch8访问方式,希望本文能给您一些参考

源码下载

  • 本篇实战的完整源码可在GitHub下载到,地址和链接信息如下表所示(https://github.com/zq2599/blog_demos)
名称 链接 备注
项目主页 https://github.com/zq2599/blog_demos 该项目在GitHub上的主页
git仓库地址(https) https://github.com/zq2599/blog_demos.git 该项目源码的仓库地址,https协议
git仓库地址(ssh) git@github.com:zq2599/blog_demos.git 该项目源码的仓库地址,ssh协议
  • 这个git项目中有多个文件夹,本次实战的源码在elasticsearch-tutorials文件夹下,如下图红框
  • elasticsearch-tutorials是个父工程,里面有多个module,本篇实战的module是crud-with-security,如下图红框

你不孤单,欣宸原创一路相伴

  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 数据库+中间件系列
  6. DevOps系列

java与es8实战之五:SpringBoot应用中操作es8(带安全检查:https、账号密码、API Key)相关推荐

  1. java与es8实战之四:SpringBoot应用中操作es8(无安全检查)

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本篇是<java与es8实战>系 ...

  2. java版gRPC实战之五:双向流

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos <java版gRPC实战>全系列链接 用p ...

  3. 阿里技术专家加多:Java异步编程实战之基于JDK中的Future实现异步编程 | 文末赠书...

    正文共:14244 字 8 图 预计阅读时间: 36 分钟 本节内容摘自<Java异步编程实战>中的一小节. 一.前言 本节主要讲解如何使用JDK中的Future实现异步编程,这包含如何使 ...

  4. 阿里技术专家加多:Java异步编程实战之基于JDK中的Future实现异步编程

    正文共:14244 字 8 图 预计阅读时间: 36 分钟 本节内容摘自<Java异步编程实战>中的一小节. 一.前言 本节主要讲解如何使用JDK中的Future实现异步编程,这包含如何使 ...

  5. java版gRPC实战之五:双向流,rabbitmq持久化原理

    responseObserver.onNext(DeductReply.newBuilder() .setCode(code) .setMessage(message) .build()); } @O ...

  6. Java类有个星号标记_Java中import包带*(星号)问题

    今天发现Java中import包时,带*(星号)的一个问题.下面附上代码和截图: 在我的电脑"E:\csdn\"目标下有两个java文件: Print.java和Demo.java ...

  7. PyQt5教程 QLineEdit 限制输入中文和中文字符,如账号密码框验证

    一.使用正则办法 from PyQt5.QtGui import QRegExpValidator from PyQt5.QtCore import QRegExp # 只能输入a~z,A~Z,0~9 ...

  8. java并发编程实战程序5-19

    验证Java并发编程实战程序清单5-19中memorizer.compute对于相同参数的请求只有一个能进Map import org.junit.Test;import java.util.conc ...

  9. Java并发编程实战之互斥锁

    文章目录 Java并发编程实战之互斥锁 如何解决原子性问题? 锁模型 Java synchronized 关键字 Java synchronized 关键字 只能解决原子性问题? 如何正确使用Java ...

最新文章

  1. Python面试题集合
  2. joomla个性定制(二)
  3. 大数据WEB阶段Mybatis(一)
  4. python语言程序设计编程题_Python语言程序设计(测试练习题)
  5. html滚动选择框代码,如何使用最简单纯Css代码美化checkbox复选框、radios单选框和滑动按钮...
  6. 【报告分享】2019云安全威胁报告.pdf(附下载链接)
  7. [英文邮件] 请求 + 感谢 + 邮件结尾 的表达整理
  8. pgsql timestamp without time zone > character varying解决方案
  9. 2021 ICPC网络赛I-A Busiest Computing Nodes(线段树)
  10. c# ascii转换方法
  11. Java-Java基础—(6)面向对象高级
  12. 《麦肯锡精英的谈判策略》 -豆瓣评分8.2
  13. 火山快手伪原创视频热门
  14. 异步请求 ajax的使用详解
  15. AWS 服务器登录和配置
  16. 侧里——我最钟爱的张国枯照片之Top10来源w
  17. EBS中AP发票分配行的解释
  18. Java打印个人基本信息
  19. Lichee_RV学习系列---认识Lichee Rv Dock、环境搭建和编译第一个程序
  20. 机器学习集成学习进阶Xgboost算法案例分析

热门文章

  1. 机器学习常用数据处理
  2. 【新知实验室】腾讯云音视频应用
  3. smc数显压力表设定方法_psi与kpa换算(smc数显压力表设定方法)
  4. 程序员,这些跨年姿势已就位,请查收~
  5. win10电脑禁用开机自启动软件
  6. hbuilder边框代码是什么_HBuilder CSS 自定义代码块
  7. K 近邻算法识别手写数字(Numpy写法)
  8. UE4 actor碰到炸弹血条减少
  9. 排序算法之快速排序实现及算法思想
  10. 单片机学习记录:流水灯