以前介紹 solr 的教程中,solr 沒有開啓權限校驗,全部的操做都是無需鑑權;當時提到,若是 solr 開啓了權限校驗,改一下 solr 的 host,帶上用戶名/密碼便可,然而真實狀況卻並不太同樣,查詢 ok,涉及到修改的操做,則會拋異常git
本文將帶你瞭解一下,這究竟是個什麼鬼畜現象github
<!-- more -->web
以前的 solr 系列教程中,經過 docker 安裝的 solr,下面的步驟也是直接針對 docker 中的 solr 進行配置,基本步驟同樣spring
具體能夠參考: 【搜索系列】Solr 環境搭建與簡單測試docker
不想看的同窗,直接用下面的命令便可:apache
docker pull solr docker run --name my-solr -d -p 8983:8983 -t solr
下面一步一步教你如何設置用戶密碼,也能夠參考博文: 手把手教你 對 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
接下來介紹一下咱們的環境
搭建一個簡單的 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
關於 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); } }
請注意,復現上面的場景時,會發現查詢沒問題,修改則會拋異常
我以前用 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
方法的調用標紅,可是不影響具體的執行哦
經過一頓 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(); }
而後測試,也是正常執行,輸出結果就不截圖了,各位小夥伴能夠親自測試一下
關於下面的這段寫法,來自: 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
信息上面的三種方式,適用於利用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");
本篇博文主要是針對須要登陸驗證的 solr 更新操做異常時,給出了四種解決方案
solr-solrj
版本到6.6.0
SolrClient
的HttpClient
爲SystemDefaultHttpClient
上面雖然給出瞭解決方法,可是爲啥有這個問題呢?
直接經過 curl 來測試一下更新 solr 操做,正常返回,並無問題,那麼這個問題到底啥緣由,到底是誰的鍋,請敬請期待後續問題定位蓋鍋定論
參考博文
系列博文
工程源碼
盡信書則不如,以上內容,純屬一家之言,因我的能力有限,不免有疏漏和錯誤之處,如發現 bug 或者有更好的建議,歡迎批評指正,不吝感激
下面一灰灰的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛