SpringBoot2.X连接ElasticSearch集成Search Guard

Table of Contents

前言

先说需求吧,我的需求:不同项目分配不同用户,用户数据独立;
但是我在网上找了很多资料都是基于证书实现连接的,疑问如下

      1.如何生成多个证书?
      2.证书如何分配权限?
      3.如何使多个证书同时生效?
      4.能否基于用户名密码方式连接
带着几点疑问开始了编程之旅!!!

废话不多数直接上代码,官方文档
1
2
3
4
5
6
7
8
9
版本介绍:
JDK:1.8
SpringBoot:2.0.1
ElasticSearch:6.6.2
Search Guard:6.6.2-24.2
需要准备证书(上篇文章中在线生成证书中有这些文件)
demouser-keystore.jks
sgadmin-keystore.jks
truststore.jks

证书来源参考:https://www.jianshu.com/p/de341fdb2789

配置目录结构及配置文件

image.png

pom.xml文件配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<properties>
<spring.boot.version>2.0.1.RELEASE</spring.boot.version>
<java.version>1.8</java.version>
<elasticsearch.version>6.6.2</elasticsearch.version>
</properties>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>${elasticsearch.version}</version>
</dependency>

<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>transport</artifactId>
<version>${elasticsearch.version}</version>
</dependency>

<!-- 添加 transport-netty4-client maven 依赖之后可以成功获取到连接 -->
<dependency>
<groupId>org.elasticsearch.plugin</groupId>
<artifactId>transport-netty4-client</artifactId>
<version>${elasticsearch.version}</version>
</dependency>

<dependency>
<groupId>com.floragunn</groupId>
<artifactId>search-guard-6</artifactId>
<version>6.6.2-24.2-api</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>
ElasticSearchClient.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
package com.isoftstone.ismart.elastic.config;

import com.floragunn.searchguard.ssl.SearchGuardSSLPlugin;
import com.floragunn.searchguard.ssl.util.SSLConfigConstants;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequest;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.transport.client.PreBuiltTransportClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.net.InetAddress;
import java.net.URL;
import java.net.URLDecoder;

/**
* @author Colin.Ye
* @version 1.0
* @ClassName ElasticSearchClient
* @date 2019/3/25
**/
@Configuration
public class ElasticSearchClient {

@Value("${spring.data.elasticsearch.cluster-nodes}")
private String nodes;

@Value("${spring.data.elasticsearch.cluster-name}")
private String custerName;

@Value("${spring.data.elasticsearch.ssl-keystore-password}")
private String sslKeystorePassword;

@Value("${spring.data.elasticsearch.ssl-truststore-password}")
private String sslTruststorePassword;

//注入的ElasticSearch实例
@Bean(name = "esClient")
public TransportClient getclient() throws Exception {
ClassLoader classLoader = ElasticSearchClient.class.getClassLoader();
URL resource = classLoader.getResource("ca/demouser-keystore.jks");
// URL resource = classLoader.getResource("ca/sgadmin-keystore.jks");
URL truresource = classLoader.getResource("ca/truststore.jks");
String keypath = URLDecoder.decode(resource.getPath(), "UTF-8");
String trupath = URLDecoder.decode(truresource.getPath(), "UTF-8");
//windows中路径会多个/ 如/E windows下需要打开注释
try {
String osName = System.getProperty("os.name");
if (StringUtils.contains(osName, "Windows")) {
if (keypath.startsWith("/")) {
keypath = keypath.substring(1, keypath.length());
}
if (trupath.startsWith("/")) {
trupath = trupath.substring(1, trupath.length());
}
}
} catch (Exception e) {
System.out.println(e);
}

Settings settings = Settings.builder()
.put("cluster.name", custerName)
.put(SSLConfigConstants.SEARCHGUARD_SSL_TRANSPORT_ENABLED, true)
.put(SSLConfigConstants.SEARCHGUARD_SSL_TRANSPORT_KEYSTORE_FILEPATH, keypath)
.put(SSLConfigConstants.SEARCHGUARD_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, trupath)
.put(SSLConfigConstants.SEARCHGUARD_SSL_TRANSPORT_KEYSTORE_PASSWORD, sslKeystorePassword)
.put(SSLConfigConstants.SEARCHGUARD_SSL_TRANSPORT_TRUSTSTORE_PASSWORD, sslTruststorePassword)
.put(SSLConfigConstants.SEARCHGUARD_SSL_HTTP_KEYSTORE_PASSWORD, sslKeystorePassword)
.put(SSLConfigConstants.SEARCHGUARD_SSL_HTTP_TRUSTSTORE_PASSWORD, sslTruststorePassword)
.put("client.transport.ignore_cluster_name", true)
.put(SSLConfigConstants.SEARCHGUARD_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, false)
.build();
TransportClient client = new PreBuiltTransportClient(settings, SearchGuardSSLPlugin.class);
// TransportClient client = new PreBuiltTransportClient(settings);
// System.out.println("Basic " + new String(Base64.encodeBase64("admin:admin".getBytes())));
// client.threadPool().getThreadContext().putHeader("Authorization",
// "Basic " + new String(Base64.encodeBase64("admin:admin".getBytes())));
try {
String[] nodeArray = nodes.split(",");
for (String node : nodeArray) {
String[] nodeArr = node.split(":");
client.addTransportAddress(new TransportAddress(InetAddress.getByName(nodeArr[0]), Integer.parseInt(nodeArr[1])));
}
} catch (Exception e) {
e.printStackTrace();
}
// client.admin().cluster().nodesInfo(new NodesInfoRequest()).actionGet();
return client;
}
}
LowCreateIndexDemo.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
package com.isoftstone.ismart.elastic.controller;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.isoftstone.ismart.elastic.model.result.Result;
import com.isoftstone.ismart.elastic.model.result.ResultCode;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.UUID;

/**
* @author Colin.Ye
* @version 1.0
* @ClassName LowCreateIndexDemo
* @date 2019/3/13
**/
@RestController
@RequestMapping("/lowClient/v1/")
@CrossOrigin
public class LowCreateIndexDemo {

@Autowired
public TransportClient client;

/**
* 创建索引
*
* @param index
* @param json
* @return
*/
@RequestMapping(value = "createIndex/{index}/{type}", method = {RequestMethod.POST, RequestMethod.PUT})
public Result createIndex(HttpServletRequest httpRequest,
@PathVariable String index, @PathVariable String type, @RequestBody String json) {
// 1、创建 创建索引request
try {
if (StringUtils.isBlank(index) || StringUtils.isBlank(type)) {
return Result.failure(ResultCode.PARAM_NOT_INDEX);
}
if (StringUtils.isBlank(json)) {
return Result.failure(ResultCode.PARAM_NOT_SETTING);
}
CreateIndexRequest request = new CreateIndexRequest(index);
JSONObject reqJson;
try {
reqJson = JSONObject.parseObject(json);
} catch (Exception e) {
return Result.failure(ResultCode.PARAM_JSON_ERROR);
}

/**
* 2、设置索引的settings
* index.number_of_shards:分片数
* index.number_of_replicas:副本数
* analysis.analyzer.default.tokenizer:默认分词器
* ik_max_word:会将文本做最细粒度的拆分
* ik_smart:会做最粗粒度的拆分
* standard:默认分词器
*/
JSONObject settingJson = reqJson.getJSONObject("settings");
Integer shards = 3;
Integer replicas = 2;
String analysis = "standard";
if (settingJson != null) {
shards = settingJson.getInteger("shards");
replicas = settingJson.getInteger("replicas");
analysis = settingJson.getString("analysis") == null ? "standard" : settingJson.getString("analysis");
}
request.settings(Settings.builder().put("index.number_of_shards", shards)
.put("index.number_of_replicas", replicas)
.put("analysis.analyzer.default.tokenizer", analysis)
);

JSONObject array = reqJson.getJSONObject("mapping");
if (array != null && array.size() > 0) {
JSONObject jsonObject = new JSONObject();
jsonObject.put(type, new JSONObject() {{
put("properties", new JSONObject() {{
for (Entry<String, Object> obj : array.entrySet()) {
put(obj.getKey(), new JSONObject() {{
put("type", obj.getValue().toString());
}});
}
}});
}});
System.out.println(jsonObject.toJSONString());
// 3、设置索引的mappings
request.mapping(type, jsonObject.toJSONString(), XContentType.JSON);
}
// 4、 设置索引的别名
// request.alias(new Alias("mmm"));
// 5、 发送请求 这里和RESTful风格不同
boolean b = setAuthHeader(httpRequest);
if (!b) {
return Result.failure(ResultCode.PERMISSION_NO_ACCESS);
}
CreateIndexResponse createIndexResponse = client.admin().indices().create(request).get();
// 6、处理响应
JSONObject jsonObject = new JSONObject();
jsonObject.put("acknowledged", createIndexResponse.isAcknowledged());
jsonObject.put("shardsAcknowledged", createIndexResponse.isAcknowledged());
return Result.success(jsonObject);
} catch (Exception e) {
e.printStackTrace();
return Result.failure(ResultCode.SPECIFIED_QUESTIONED_USER_NOT_EXIST, e.getMessage());
}
}

/**
* 添加文档
*
* @param index
* @return
*/
@RequestMapping(value = "/save/{index}/{type}", method = RequestMethod.POST)
public Result save(HttpServletRequest httpRequest, @PathVariable String index, @PathVariable String type, @RequestBody String json) {
if (StringUtils.isBlank(index) || StringUtils.isBlank(type)) {
return Result.failure(ResultCode.PARAM_NOT_INDEX);
}
if (StringUtils.isBlank(json)) {
return Result.failure(ResultCode.PARAM_NOT_SETTING);
}
JSONObject jsonObject = JSONObject.parseObject(json);
if (jsonObject == null) {
return Result.failure(ResultCode.PARAM_IS_BLANK);
}
String id = jsonObject.getString("id");
if (StringUtils.isBlank(id)) {
id = UUID.randomUUID().toString().replaceAll("-", "");
}
boolean b = setAuthHeader(httpRequest);
if (!b) {
return Result.failure(ResultCode.PERMISSION_NO_ACCESS);
}
IndexResponse response = client.prepareIndex(index, type, id).setSource(jsonObject.getInnerMap()).execute().actionGet();
return Result.success(response.toString());
}

/**
* 查询数据
*
* @param index
* @param type
* @param json
* @return
*/
@RequestMapping(value = "/search/{index}/{type}/{page}/{pageSize}", method = RequestMethod.POST)
public Result search(HttpServletRequest httpRequest,
@PathVariable String index,
@PathVariable String type,
@PathVariable Integer page,
@PathVariable Integer pageSize, @RequestBody String json) {
try {
// 构造查询对象的工厂类 QueryBuilders,matchQuery全文查询,Operator.AND指定分词项之间采用AND方式连接,默认是OR
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
// 3.设置boolQueryBuilder条件
// 子boolQueryBuilder条件条件,用来表示查询条件or的关系

JSONObject jsonObject = JSONObject.parseObject(json);
for (Map.Entry<String, Object> entry : jsonObject.entrySet()) {
boolQueryBuilder.must(QueryBuilders.matchQuery(entry.getKey(), entry.getValue().toString()));
}

BoolQueryBuilder childBoolQueryBuilder = new BoolQueryBuilder()
.should(QueryBuilders.matchPhraseQuery("comment_content", "1"))
.should(QueryBuilders.matchPhraseQuery("comment_content", "2"));
// 4.添加查询条件到boolQueryBuilder中
// boolQueryBuilder
// .must(childBoolQueryBuilder);
// .must(QueryBuilders.matchQuery());


//构造HighlightBuilder对象,设置需要高亮的字段并自定义高亮标签
HighlightBuilder highlighter = new HighlightBuilder()
.field("comment_content")
.preTags("<span stype=\"color:red\">")
.postTags("</span>");

boolean b = setAuthHeader(httpRequest);
if (!b) {
return Result.failure(ResultCode.PERMISSION_NO_ACCESS);
}
SearchResponse response = client.prepareSearch(index)
.setTypes(type)
.setQuery(boolQueryBuilder)
.highlighter(highlighter)
.setSize(pageSize)
.setFrom(page)
// .addSort("create_time", SortOrder.DESC)
.get();

//通过上面获得的SearchResponse对象,取得返回结果
SearchHits hits = response.getHits();
//搜索到的结果数
// System.out.println("共搜索到:" + hits.getTotalHits());

JSONArray array = new JSONArray();
//遍历SearchHits数组
for (SearchHit hit : hits) {
array.add(JSONObject.parse(hit.getSourceAsString()));
// System.out.println("Source:" + hit.getSourceAsString());//返回String类型的文档内容
// System.out.println("Source As Map:" + hit.getSource());//返回Map格式的文档内容
// System.out.println("Index:" + hit.getIndex());//返回文档所在的索引
// System.out.println("Type:" + hit.getType());//返回文档所在的类型
// System.out.println("ID:" + hit.getId());//返回文档的id
// System.out.println("Source:" + hit.getSource().get("price"));//从返回的map中通过key取到value
// System.out.println("Score:" + hit.getScore());//返回文档的评分
//getHighlightFields()会返回文档中所有高亮字段的内容,再通过get()方法获取某一个字段的高亮片段,最后调用getFragments()方法,返回Text类型的数组
// Text[] texts = hit.getHighlightFields().get("title").getFragments();
// if(texts != null) {
// //遍历高亮结果数组,取出高亮内容
// for (Text text : texts) {
// System.out.println(text.string());
// }
// }
}
return Result.success(array);
}catch (Exception e){
return Result.failure(ResultCode.SPECIFIED_QUESTIONED_USER_NOT_EXIST, e.getMessage());
}
}
// 获取请求消息头中的用户信息,格式【用户名:密码】
private boolean setAuthHeader(HttpServletRequest httpRequest) {
String authorization = httpRequest.getHeader("Authorization");
if (StringUtils.isNotBlank(authorization)) {
client.threadPool().getThreadContext().putHeader("Authorization",
"Basic " + new String(Base64.encodeBase64(authorization.getBytes())));
return true;
}
return false;
}
}
效果图
  • 使用colin用户访问colin数据
    image.png

  • 使用colin用户访问其他索引数据
    image.png

  • 使用admin用户访问数据
    image.png

结语 :

经过一天时间总算是实现了,但是为什么这样就行,我也不知道!!!
  • 使用sgadmin-keystore.jks,不需要在请求时添加消息头
  • 使用demouser-keystore.jks,需要每次请求时添加用户信息消息头

借鉴文章如下:
SearchGuard 实践
elasticsearch系列七:ES Java客户端-Elasticsearch Java client
Elasticsearch使用searchguard后Java连接及安全验证

相关文章

评论系统未开启,无法评论!