SpringBoot2.X连接ElasticSearch集成Se
2019-03-28 本文已影响0人
丿捺人生
前言
先说需求吧,我的需求:不同项目分配不同用户,用户数据独立;
但是我在网上找了很多资料都是基于证书实现连接的,疑问如下
1.如何生成多个证书?
2.证书如何分配权限?
3.如何使多个证书同时生效?
4.能否基于用户名密码方式连接
带着几点疑问开始了编程之旅!!!
废话不多数直接上代码,官方文档
版本介绍:
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.pngpom.xml文件配置
<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
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
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,需要每次请求时添加用户信息消息头
存疑:es服务中没有配置demouser证书,为什么java client配置demouser证书后账号密码方式就生效了?
借鉴文章如下:
SearchGuard 实践
elasticsearch系列七:ES Java客户端-Elasticsearch Java client
Elasticsearch使用searchguard后Java连接及安全验证