美文网首页JAVA随笔码农日历
【SpringBoot 搜索系列】Solr 身份认证与授权更新异

【SpringBoot 搜索系列】Solr 身份认证与授权更新异

作者: 一灰灰blog | 来源:发表于2020-04-01 20:02 被阅读0次
    image

    【搜索系列】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);
        }
    }
    
    image

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

    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

    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

    II. 其他

    0. 系列博文&工程源码

    参考博文

    系列博文

    工程源码

    1. 一灰灰 Blog

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

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

    一灰灰blog

    相关文章

      网友评论

        本文标题:【SpringBoot 搜索系列】Solr 身份认证与授权更新异

        本文链接:https://www.haomeiwen.com/subject/ygfzuhtx.html