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

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

image.png


之前介绍 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);
    }
}
复制代码

image.png


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


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方法的调用标红,但是不影响具体的执行哦


image.png


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
  • 指定SolrClientHttpClientSystemDefaultHttpClient
  • HttpClient 拦截器
  • SolrRequest 指定用户名密码


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


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


image.png


相关文章
|
19天前
|
Web App开发 JavaScript Java
elasticsearch学习五:springboot整合 rest 操作elasticsearch的 实际案例操作,编写搜索的前后端,爬取京东数据到elasticsearch中。
这篇文章是关于如何使用Spring Boot整合Elasticsearch,并通过REST客户端操作Elasticsearch,实现一个简单的搜索前后端,以及如何爬取京东数据到Elasticsearch的案例教程。
131 0
elasticsearch学习五:springboot整合 rest 操作elasticsearch的 实际案例操作,编写搜索的前后端,爬取京东数据到elasticsearch中。
|
20天前
|
Java API Spring
springBoot:注解&封装类&异常类&登录实现类 (八)
本文介绍了Spring Boot项目中的一些关键代码片段,包括使用`@PathVariable`绑定路径参数、创建封装类Result和异常处理类GlobalException、定义常量接口Constants、自定义异常ServiceException以及实现用户登录功能。通过这些代码,展示了如何构建RESTful API,处理请求参数,统一返回结果格式,以及全局异常处理等核心功能。
|
1月前
|
Java 关系型数据库 数据库连接
SpringBoot项目使用yml文件链接数据库异常
【10月更文挑战第3天】Spring Boot项目中数据库连接问题可能源于配置错误或依赖缺失。YAML配置文件的格式不正确,如缩进错误,会导致解析失败;而数据库驱动不匹配、连接字符串或认证信息错误同样引发连接异常。解决方法包括检查并修正YAML格式,确认配置属性无误,以及添加正确的数据库驱动依赖。利用日志记录和异常信息分析可辅助问题排查。
100 10
|
1月前
|
Java 关系型数据库 MySQL
SpringBoot项目使用yml文件链接数据库异常
【10月更文挑战第4天】本文分析了Spring Boot应用在连接数据库时可能遇到的问题及其解决方案。主要从四个方面探讨:配置文件格式错误、依赖缺失或版本不兼容、数据库服务问题、配置属性未正确注入。针对这些问题,提供了详细的检查方法和调试技巧,如检查YAML格式、验证依赖版本、确认数据库服务状态及用户权限,并通过日志和断点调试定位问题。
|
3月前
|
前端开发 小程序 Java
【规范】SpringBoot接口返回结果及异常统一处理,这样封装才优雅
本文详细介绍了如何在SpringBoot项目中统一处理接口返回结果及全局异常。首先,通过封装`ResponseResult`类,实现了接口返回结果的规范化,包括状态码、状态信息、返回信息和数据等字段,提供了多种成功和失败的返回方法。其次,利用`@RestControllerAdvice`和`@ExceptionHandler`注解配置全局异常处理,捕获并友好地处理各种异常信息。
778 0
【规范】SpringBoot接口返回结果及异常统一处理,这样封装才优雅
|
3月前
|
安全 Java UED
掌握SpringBoot单点登录精髓,单点登录是一种身份认证机制
【8月更文挑战第31天】单点登录(Single Sign-On,简称SSO)是一种身份认证机制,它允许用户只需在多个相互信任的应用系统中登录一次,即可访问所有系统,而无需重复输入用户名和密码。在微服务架构日益盛行的今天,SSO成为提升用户体验和系统安全性的重要手段。本文将详细介绍如何在SpringBoot中实现SSO,并附上示例代码。
60 0
|
3月前
|
消息中间件 Java 开发工具
【Azure 事件中心】Spring Cloud Stream Event Hubs Binder 发送Event Hub消息遇见 Spec. Rule 1.3 - onSubscribe, onNext, onError and onComplete signaled to a Subscriber MUST be signaled serially 异常
【Azure 事件中心】Spring Cloud Stream Event Hubs Binder 发送Event Hub消息遇见 Spec. Rule 1.3 - onSubscribe, onNext, onError and onComplete signaled to a Subscriber MUST be signaled serially 异常
|
3月前
|
Java Spring
【Azure 事件中心】Spring Boot 集成 Event Hub(azure-spring-cloud-stream-binder-eventhubs)指定Partition Key有异常消息
【Azure 事件中心】Spring Boot 集成 Event Hub(azure-spring-cloud-stream-binder-eventhubs)指定Partition Key有异常消息
|
3月前
|
Java Spring
【Azure 服务总线】Spring Cloud 的应用 使用Service Bus 引起 org.springframework.beans.BeanInstantiationException 异常,无法启动
【Azure 服务总线】Spring Cloud 的应用 使用Service Bus 引起 org.springframework.beans.BeanInstantiationException 异常,无法启动
|
3月前
|
NoSQL Java Redis
【Azure Spring Cloud】Java Spring Cloud 应用部署到Azure上后,发现大量的 java.lang.NullPointerException: null at io.lettuce.core.protocol.CommandHandler.writeSingleCommand(CommandHandler.java:426) at ... 异常
【Azure Spring Cloud】Java Spring Cloud 应用部署到Azure上后,发现大量的 java.lang.NullPointerException: null at io.lettuce.core.protocol.CommandHandler.writeSingleCommand(CommandHandler.java:426) at ... 异常

热门文章

最新文章