之前介紹 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:[email protected]: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:[email protected]: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 操作,正常返回,並沒有問題,那麼這個問題到底啥原因,究竟是誰的鍋,請敬請期待後續問題定位蓋鍋定論
II. 其他
0. 系列博文&工程源碼
參考博文
系列博文
- 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