0
点赞
收藏
分享

微信扫一扫

【SpringBoot 搜索系列】Solr 身份认证与授权更新异常解决方案


【SpringBoot 搜索系列】Solr 身份认证与授权更新异常解决方案_solr

​​【搜索系列】Solr 身份认证与授权更新异常解决方案​​

之前介绍 solr 的教程中,solr 没有开启权限校验,所有的操作都是无需鉴权;当时提到,如果 solr 开启了权限校验,改一下 solr 的 host,带上用户名/密码即可,然而真实情况却并不太一样,查询 ok,涉及到修改的操作,则会抛异常

本文将带你了解一下,这到底是个什么鬼畜现象

I. Solr 配置用户登录

1. 安装

之前的 solr 系列教程中,通过 docker 安装的 solr,下面的步骤也是直接针对 docker 中的 solr 进行配置,基本步骤一样

具体可以参考: ​​【搜索系列】Solr 环境搭建与简单测试​​

不想看的同学,直接用下面的命令即可:

docker pull solr
docker run --name my-solr -d -p 8983:8983 -t solr

2. 配置

下面一步一步教你如何设置用户密码,也可以参考博文: ​​手把手教你 对 solr8 配置用户登录验证​​

进入实例,注意使用​​root​​用户,否则某些操作可能没有权限

docker exec  -u root -it my-solr /bin/bash

创建鉴权文件

vim server/etc/verify.properties

内容如下,格式为 ​​用户名:密码,权限​​, 一行一个账号

root:123,admin

配置鉴权文件

vim server/contexts/solr-jetty-context.xml

添加下面的内容放在​​Configure​​标签内

<Get name="securityHandler">
<Set name="loginService">
<New class="org.eclipse.jetty.security.HashLoginService">
<Set name="name">verify—name</Set>
<Set name="config"><SystemProperty name="jetty.home" default="."/>/etc/verify.properties</Set>
</New>
</Set>
</Get>

修改 web.xml

vim server/solr-webapp/webapp/WEB-INF/web.xml

在​​security-constraint​​标签下面,新增

<login-config>
<auth-method>BASIC</auth-method>
<!-- 请注意,这个name 和上面的Set标签中的name保持一致 -->
<realm-name>verify-name</realm-name>
</login-config>

重启 solr,配置生效

docker restart my-solr

II. 场景复现

接下来介绍一下我们的环境

  • springboot: 2.2.1.RELEASE
  • solr: 8.0

1. 项目环境

搭建一个简单的 springboot 项目,xml 依赖如下

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-solr</artifactId>
</dependency>

<!-- 请注意,在solr开启登录验证时,这个依赖必须有 -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
</dependencies>

<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</pluginManagement>
</build>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/libs-snapshot-local</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone-local</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>https://repo.spring.io/libs-release-local</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>

对应的配置文件​​application.yml​

spring:
data:
solr:
# 请注意,用户名密码直接写在了url中
host: http://root:123@127.0.0.1:8983/solr

2. 复现

关于 solr 的基本操作,如果有疑问的小伙伴可以翻一下我之前的搜索系列博文,满足你的扫盲需求;

核心的 solr 操作实例如下:

@Data
public class DocDO implements Serializable {
private static final long serialVersionUID = 7245059137561820707L;
@Id
@Field("id")
private Integer id;
@Field("content_id")
private Integer contentId;
@Field("title")
private String title;
@Field("content")
private String content;
@Field("type")
private Integer type;
@Field("create_at")
private Long createAt;
@Field("publish_at")
private Long publishAt;
}

@Component
public class SolrOperater {

@Autowired
private SolrTemplate solrTemplate;


public void operate() {
testAddByDoc();
queryById();
}

public void testAddByDoc() {
SolrInputDocument document = new SolrInputDocument();
document.addField("id", 999999);
document.addField("content_id", 3);
document.addField("title", "testAddByDoc!");
document.addField("content", "新增哒哒哒");
document.addField("type", 2);
document.addField("create_at", System.currentTimeMillis() / 1000);
document.addField("publish_at", System.currentTimeMillis() / 1000);

UpdateResponse response = solrTemplate.saveDocument("yhh", document, Duration.ZERO);
solrTemplate.commit("yhh");
System.out.println("over:" + response);
}

private void queryById() {
DocDO ans = solrTemplate.getById("yhh", 999999, DocDO.class).get();
System.out.println("queryById: " + ans);
}
}

​SolrTemplat​​定义如下

@Configuration
public class SearchAutoConfig {
@Bean
@ConditionalOnMissingBean(SolrTemplate.class)
public SolrTemplate solrTemplate(SolrClient solrClient) {
return new SolrTemplate(solrClient);
}
}

开始测试

@SpringBootApplication
public class Application {

public Application(SolrOperater solrOperater) {
solrOperater.operate();
}

public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}

【SpringBoot 搜索系列】Solr 身份认证与授权更新异常解决方案_solr_02

请注意,复现上面的场景时,会发现查询没问题,修改则会抛异常

3. 解决方案

a. 降版本

我之前用 solr 的时候,也是上面的操作方式,然而并没有出现过这种问题,这就有点蛋疼了;

找之前的项目查看版本,发现之前用的​​solr-solrj​​​用的是​​6.6.5​​​,换个版本试一下(默认的版本是​​8.2.0​​)

<dependency>
<groupId>org.apache.solr</groupId>
<artifactId>solr-solrj</artifactId>
<version>6.6.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-solr</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.solr</groupId>
<artifactId>solr-solrj</artifactId>
</exclusion>
</exclusions>
</dependency>

见证奇迹的时刻到了,执行正常了,虽然​​saveDocument​​方法的调用标红,但是不影响具体的执行哦

【SpringBoot 搜索系列】Solr 身份认证与授权更新异常解决方案_spring boot_03

b. SystemDefaultHttpClient

通过一顿 debug,单步执行,终于找到为啥​​6.6.5​​​版本的​​solr-solrj​​​可以正常操作,而​​8.2.0​​却不行(如果想知道这一枯燥的过程,请评论告诉我,否则我也不知道啥时候可以看到 😂)

关键的问题就是旧版本的用的是​​SystemDefaultHttpClient​​​来实现 solr 的沟通;新版本使用的是​​InternalHttpClient​

那么一个可用的解决方法就是不降版本,改为指定 Solr 的​​HttpClient​

在配置类中,如下操作:

@Bean
public HttpSolrClient solrClient() {
HttpClient httpClient = new SystemDefaultHttpClient();
return new HttpSolrClient.Builder(url).withHttpClient(httpClient).build();
}

然后测试,也是正常执行,输出结果就不截图了,各位小伙伴可以亲自测试一下

c. HttpClient 拦截器

关于下面的这段写法,来自: ​​Preemptive Basic authentication with Apache HttpClient 4​​

上面的方式虽然可以让我们正确操作 solr 了,但是​​SystemDefaultHttpClient​​有一个删除注解,也就是说不建议再直接用它了,那就借鉴它的使用方式,来满足我们的需求,所以可以如下操作

@Value("${spring.data.solr.host}")
private String url;

@Data
public static class UrlDo {
private String url;

private String user;
private String pwd;

private String host;
private int port;

public static UrlDo parse(String url) throws MalformedURLException {
// http://root:123@127.0.0.1:8983/solr
URL u = new URL(url);
UrlDo out = new UrlDo();
out.setHost(u.getHost());
out.setPort(u.getPort());

String userInfo = u.getUserInfo();
if (!StringUtils.isEmpty(userInfo)) {
String[] users = org.apache.commons.lang3.StringUtils.split(userInfo, ":");
out.setUser(users[0]);
out.setPwd(users[1]);
}
out.setUrl(url);
return out;
}
}

public class SolrAuthInterceptor implements HttpRequestInterceptor {
@Override
public void process(final HttpRequest request, final HttpContext context) {
AuthState authState = (AuthState) context.getAttribute(HttpClientContext.TARGET_AUTH_STATE);
if (authState.getAuthScheme() == null) {
CredentialsProvider credsProvider =
(CredentialsProvider) context.getAttribute(HttpClientContext.CREDS_PROVIDER);
HttpHost targetHost = (HttpHost) context.getAttribute(HttpCoreContext.HTTP_TARGET_HOST);
AuthScope authScope = new AuthScope(targetHost.getHostName(), targetHost.getPort());
Credentials creds = credsProvider.getCredentials(authScope);
authState.update(new BasicScheme(), creds);
}
}
}

@Bean
public HttpSolrClient solrClient() throws MalformedURLException {
UrlDo urlDo = UrlDo.parse(url);
CredentialsProvider provider = new BasicCredentialsProvider();
provider.setCredentials(new AuthScope(urlDo.getHost(), urlDo.getPort()),
new UsernamePasswordCredentials(urlDo.getUser(), urlDo.getPwd()));

HttpClientBuilder builder = HttpClientBuilder.create();
// 请注意下面这一行,指定拦截器,用于设置认证信息
builder.addInterceptorFirst(new SolrAuthInterceptor());
builder.setDefaultCredentialsProvider(provider);
CloseableHttpClient httpClient = builder.build();
return new HttpSolrClient.Builder(url).withHttpClient(httpClient).build();
}

上面的实现有点长,简单的拆解一下

  • ​UrlDo​​​: 解析 solr 的 url,得到我们需要的​​host + port + user + password​
  • ​solrClient​​​: 在创建​​SolrClient​​ bean 实例时,指定相应的授权信息
  • ​SolrAuthInterceptor​​​: 自定义拦截器,更新​​authState​​信息

d. SolrRequest

上面的三种方式,适用于利用​​SolrClient​​​或者​​SolrTemplate​​​来操作的 solr;当然我可以完全抛弃掉它们,直接使用​​SolrRequest​​来操作,如下

SolrInputDocument document = new SolrInputDocument();
document.addField("id", 999999);
document.addField("content_id", 3);
document.addField("title", "testAddByDoc!");
document.addField("content", "新增哒哒哒");
document.addField("type", 2);
document.addField("create_at", System.currentTimeMillis() / 1000);
document.addField("publish_at", System.currentTimeMillis() / 1000);

UpdateRequest updateRequest = new UpdateRequest();
updateRequest.setBasicAuthCredentials("root", "123");
updateRequest.add(document);
UpdateResponse response = updateRequest.process(solrClient, "yhh");
updateRequest.commit(solrClient, "yhh");

4. 小结

本篇博文主要是针对需要登录验证的 solr 更新操作异常时,给出了四种解决方案

  • 降​​solr-solrj​​​版本到​​6.6.0​
  • 指定​​SolrClient​​​的​​HttpClient​​​为​​SystemDefaultHttpClient​
  • HttpClient 拦截器
  • SolrRequest 指定用户名密码

上面虽然给出了解决方法,但是为啥有这个问题呢?

直接通过 curl 来测试一下更新 solr 操作,正常返回,并没有问题,那么这个问题到底啥原因,究竟是谁的锅,请敬请期待后续问题定位盖锅定论

【SpringBoot 搜索系列】Solr 身份认证与授权更新异常解决方案_spring boot_04

II. 其他

0. 系列博文&工程源码

参考博文

  • ​​手把手教你 对 solr8 配置用户登录验证​​
  • ​​Preemptive Basic authentication with Apache HttpClient 4​​

系列博文

  • ​​200115-SpringBoot 系列教程 Solr 之查询使用姿势小结​​
  • ​​200114-SpringBoot 系列教程 Solr 之文档删除​​
  • ​​190526-SpringBoot 高级篇搜索 Solr 之文档新增与修改使用姿势​​
  • ​​190510-SpringBoot 高级篇搜索之 Solr 环境搭建与简单测试​​

工程源码

  • 工程:​​https://github.com/liuyueyi/spring-boot-demo​​
  • 源码:​​https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-boot/141-search-solr-auth​​

1. 一灰灰 Blog

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激

下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

  • 一灰灰 Blog 个人博客 ​​https://blog.hhui.top​​
  • 一灰灰 Blog-Spring 专题博客 ​​http://spring.hhui.top​​

【SpringBoot 搜索系列】Solr 身份认证与授权更新异常解决方案_spring_05


举报

相关推荐

0 条评论