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

java,es8,实战,springboot,应用,操作,安全检查,https,账号密码,api,key · 浏览次数 : 290

小编点评

**内容生成时需要带简单的排版** * **使用标题**将文章内容分成多个标题 * **使用星号**将文章内容分成多个星号 * **使用斜杠**将文章内容分成多个斜杠 * **使用空格**将文章内容分成多个空格 * **使用脚注**将文章内容分成多个脚注 * **使用颜色**将文章内容分成多个颜色 **示例** **标题** * 基于SpringData提供elasticsearch8操作的实战 **星号** * 使用SpringData提供的elasticsearch8操作进行创建、删除、查找索引操作 **斜杠** * 使用SpringData提供的elasticsearch8操作进行创建、删除、查找索引操作 **空格** * 使用SpringData提供的elasticsearch8操作进行创建、删除、查找索引操作 **脚注** * 使用SpringData提供的elasticsearch8操作进行创建、删除、查找索引操作 **颜色** * 使用SpringData提供的elasticsearch8操作进行创建、删除、查找索引操作 **示例代码** ```java //标题 title(" 基于SpringData提供elasticsearch8操作的实战") //星号 star(" 使用SpringData提供的elasticsearch8操作进行创建、删除、查找索引操作") //斜杠 slash(" 使用SpringData提供的elasticsearch8操作进行创建、删除、查找索引操作") //空格 space("使用SpringData提供的elasticsearch8操作进行创建、删除、查找索引操作") //脚注 note(" 使用SpringData提供的elasticsearch8操作进行创建、删除、查找索引操作") //颜色 color(color("红色"), color("红色"), color("红色")) ```

正文

欢迎访问我的GitHub

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

本篇概览

  • 本篇是《java与es8实战》系列的第五篇,总体目标明确:实战在SpringBoot应用中操作elasticsearch8,今天的重点是SpringBoot应用连接带有安全检查的elasticsearch8服务端
  • 连接需要安全检查的elasticsearch8是为了更接近真实环境,首先,连接是基于自签证书的https协议,其次,认证方式有两种
  1. 第一种是账号密码
  2. 第二种是es服务端授权的API Key

在这里插入图片描述

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

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

创建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: elastic
  passwd: 123456
  apikey: ZVVWMVY0RUJ1Y0dJeHBiZXJHdUo6WXloU1RoOUVUejJMS0JrMy1JeTJldw==
  # 多个IP逗号隔开
  hosts: 127.0.0.1:9200

编码:启动类

  • 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对象的时候,属于前面总结的套路步骤中的一步,如下图红框所示

image-20220710215632738

  • 配置类的详细代码如下,有几处需要注意的地方稍后会说明
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 {

    @Setter
    private String hosts;

    @Setter
    private String username;

    @Setter
    private String passwd;

    @Setter
    private 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;
    }

    @Bean
    public 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());
    }

    @Bean
    public ElasticsearchClient clientByApiKey() throws Exception {
        ElasticsearchTransport transport = getElasticsearchTransport(apikey, toHttpHost());
        return new ElasticsearchClient(transport);
    }
}
  • 上述代码有以下几处需要注意
  1. 这个配置类为业务代码提供了两个服务bean,作用是操作es,这两个服务bean分别由clientByPasswdclientByApiKey两个方法负责提供
  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 {

    @Autowired
    ESService esService;

    @Test
    void 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访问方式,希望本文能给您一些参考

源码下载

名称 链接 备注
项目主页 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,如下图红框
    在这里插入图片描述

欢迎关注博客园:程序员欣宸

学习路上,你不孤单,欣宸原创一路相伴...

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

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

SpringBoot应用中操作es8,本篇重点是如何连接带安全检查的es8服务端(https、账号密码、API Key)

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

最简单的,在SpringBoot应用中使用ES官方库操作ES8

java与es8实战之六:用JSON创建请求对象(比builder pattern更加直观简洁)

向ES发送请求时,如何创建请求对象呢?官方推荐的builder patter,在面对复杂的请求对象结构时还好用吗?有没有更加直观简洁的方法,尽在本文一网打尽

java与es8实战之三:Java API Client有关的知识点串讲

正式使用官方的Java API Client操作ES之前,将与之有关的重要知识点先做一轮串讲,后面开始编码时,疑点已扫清,可以愉快而顺畅的实现业务功能

java与es8实战之一:以builder pattern开篇

欣宸原创《java与es8实战》系列的开篇,重温经典,夯实基础,再出发

java与es8实战之二:实战前的准备工作

编码实战前的准备工作,包括搭建环境,列举各软件参考版本,选择合适的库,创建父工程等

Java与React轻松导出Excel/PDF数据

前言 在B/S架构中,服务端导出是一种高效的方式。它将导出的逻辑放在服务端,前端仅需发起请求即可。通过在服务端完成导出后,前端再下载文件完成整个导出过程。服务端导出具有许多优点,如数据安全、适用于大规模数据场景以及不受前端性能影响等。 本文将使用前端框架React和服务端框架Spring Boot搭

[转帖]【JVM】Java内存区域与OOM

引入 Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的“高墙”,墙外面的人想进去,墙里面的人却想出来。 Java虚拟机运行时数据区 如图所示 1.程序计数器(线程私有) 作用 记录当前线程所执行到的字节码的行号。字节码解释器工作的时候就是通过改变这个计数器的值来选取下一条需要执行的字节

[转帖]【JVM】Java内存区域与OOM

引入 Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的“高墙”,墙外面的人想进去,墙里面的人却想出来。 Java虚拟机运行时数据区 如图所示 1.程序计数器(线程私有) 作用 记录当前线程所执行到的字节码的行号。字节码解释器工作的时候就是通过改变这个计数器的值来选取下一条需要执行的字节

Java反射与Fastjson的危险反序列化

Preface 在前文中,我们介绍了 Java 的基础语法和特性和 fastjson 的基础用法,本文我们将深入学习fastjson的危险反序列化以及预期相关的 Java 概念。 什么是Java反射? 在前文中,我们有一行代码 Computer macBookPro = JSON.parseObje