【SpringBoot 搜索系列】Solr 身份認證與受權更新異常解決方案

【搜索系列】Solr 身份認證與受權更新異常解決方案java

以前介紹 solr 的教程中,solr 沒有開啓權限校驗,全部的操做都是無需鑑權;當時提到,若是 solr 開啓了權限校驗,改一下 solr 的 host,帶上用戶名/密碼便可,然而真實狀況卻並不太同樣,查詢 ok,涉及到修改的操做,則會拋異常git

本文將帶你瞭解一下,這究竟是個什麼鬼畜現象github

<!-- more -->web

I. Solr 配置用戶登陸

1. 安裝

以前的 solr 系列教程中,經過 docker 安裝的 solr,下面的步驟也是直接針對 docker 中的 solr 進行配置,基本步驟同樣spring

具體能夠參考: 【搜索系列】Solr 環境搭建與簡單測試docker

不想看的同窗,直接用下面的命令便可:apache

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

2. 配置

下面一步一步教你如何設置用戶密碼,也能夠參考博文: 手把手教你 對 solr8 配置用戶登陸驗證vim

進入實例,注意使用root用戶,不然某些操做可能沒有權限springboot

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

建立鑑權文件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);
    }
}

請注意,復現上面的場景時,會發現查詢沒問題,修改則會拋異常

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方法的調用標紅,可是不影響具體的執行哦

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 操做,正常返回,並無問題,那麼這個問題到底啥緣由,到底是誰的鍋,請敬請期待後續問題定位蓋鍋定論

II. 其餘

0. 系列博文&工程源碼

參考博文

系列博文

工程源碼

1. 一灰灰 Blog

盡信書則不如,以上內容,純屬一家之言,因我的能力有限,不免有疏漏和錯誤之處,如發現 bug 或者有更好的建議,歡迎批評指正,不吝感激

下面一灰灰的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛

一灰灰blog

相關文章
相關標籤/搜索