0

I have configure Solr 6.2.1 as SolrCloud. Later I have Configured Basic Authentication. I am going to configure Spring data solr 2.0.4.RELEASE with Solrj 6.2 and this is my code:

@Configuration
@EnableSolrRepositories(basePackages = { "ir.saeed.server.solr" }, multicoreSupport = true)

public class SearchContext {

    @Value("${solr.host}")

    private String host;


    @Value("${solr.port}")

    private Integer port;


    @Value("${solr.username}")

    private String username;


    @Value("${solr.password}")

    private String password;


    @Value("${zkHost}")

    private String zkHost;


    @Value("${solr.coreName}")

    private String collectionName;


    @Bean

    public SolrTemplate solrTemplate() {

        return new SolrTemplate(solrClientFactory());

    }


    @Bean

    public BasicCredentialsProvider credentialsProvider() {

        BasicCredentialsProvider provider = new BasicCredentialsProvider();

        provider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));

        return provider;

    }


    @Bean

    public SolrClientFactory solrClientFactory() {

        return new HttpSolrClientFactory(solrClient(), "", credentialsProvider().getCredentials(AuthScope.ANY), "BASIC");

    }


    @Bean

    public SolrClient solrClient() {

        return new CloudSolrClient.Builder().withZkHost(zkHost).build();

    }

}

But when i run my web application this Exception occures:

 10:51:48,110 org.springframework.data.solr.UncategorizedSolrException: nested exception is java.lang.NullPointerException


 10:51:48,111   at org.springframework.data.solr.core.SolrTemplate.execute(SolrTemplate.java:172)


 10:51:48,111   at org.springframework.data.solr.core.SolrTemplate.executeSolrQuery(SolrTemplate.java:509)


 10:51:48,111   at org.springframework.data.solr.core.SolrTemplate.query(SolrTemplate.java:504)


 10:51:48,111   at org.springframework.data.solr.core.SolrTemplate.doQueryForPage(SolrTemplate.java:338)


 10:51:48,111   at org.springframework.data.solr.core.SolrTemplate.queryForPage(SolrTemplate.java:350)

How can I resolve the issue? I think my Configuration is incorrect

Roman Melnyk
  • 1,097
  • 1
  • 8
  • 21
Saeed Shahsavan
  • 35
  • 1
  • 10
  • I have changed my Configuration now i use solrj-5.5 and ZooKeeper 3.4.6 and i have changed this line: return new CloudSolrClient.Builder().withZkHost(zkHost).build(); to: return new CloudSolrClient(zkURL); but my issue did not solve now this Exception Occures: 11:08:43,827 org.springframework.data.solr.UncategorizedSolrException: Error from server 11:08:43,827 11:08:43,827 11:08:43,827 Error 401 require authentication 11:08:43,827 – Saeed Shahsavan Oct 30 '16 at 08:05

4 Answers4

1

I found a workaround for those who has same issue Extends your own HttpSolrClientFactory. the problem caused by LBHttpSolrClient httpClient not being setup properly. correct setup should be like the following block if (solrClient instanceof LBHttpSolrClient) {...}

AuthHttpSolrClientFactory.java

@SuppressWarnings("deprecation") public class AuthHttpSolrClientFactory extends HttpSolrClientFactory {

public AuthHttpSolrClientFactory(SolrClient solrClient, String core, Credentials credentials, String authPolicy) {
    super(solrClient, core, credentials, authPolicy);
    Assert.notNull(solrClient, "solrClient must not be null");
    if (authPolicy != null) {
        Assert.hasText(authPolicy);
    }
    appendBasicAuthentication(credentials, authPolicy, this.getSolrClient());
}

private void appendBasicAuthentication(Credentials credentials, String authPolicy,  SolrClient solrClient) {
    if( credentials != null) {
        if (solrClient instanceof HttpSolrClient) {
            HttpSolrClient httpSolrClient = (HttpSolrClient) solrClient;
            if (assertHttpClientInstance(httpSolrClient.getHttpClient())) {
                AbstractHttpClient httpClient = (AbstractHttpClient) httpSolrClient.getHttpClient();
                httpClient.getCredentialsProvider().setCredentials(new AuthScope(AuthScope.ANY), credentials);
                httpClient.getParams().setParameter(AuthPNames.TARGET_AUTH_PREF, Arrays.asList(authPolicy));
            }
        }
        else if (solrClient instanceof LBHttpSolrClient) {
            LBHttpSolrClient lbhttpSolrClient = (LBHttpSolrClient) solrClient;
            if (assertHttpClientInstance(lbhttpSolrClient.getHttpClient())) {
                AbstractHttpClient httpClient = (AbstractHttpClient) lbhttpSolrClient.getHttpClient();
                httpClient.getCredentialsProvider().setCredentials(new AuthScope(AuthScope.ANY), credentials);
                httpClient.getParams().setParameter(AuthPNames.TARGET_AUTH_PREF, Arrays.asList(authPolicy));
            }
        }
    }
}

private boolean assertHttpClientInstance(HttpClient httpClient) {
    Assert.isInstanceOf(AbstractHttpClient.class, httpClient,
            "HttpClient has to be derivate of AbstractHttpClient in order to allow authentication.");
    return true;
}

}

beans-solr.xml

<solr:solr-client id="solrClient" url="${solr.host}" />
<bean id="credentials" class="org.apache.http.auth.UsernamePasswordCredentials">
    <constructor-arg type="java.lang.String" value="${solr.credentials}"/>
</bean>
<bean id="solrClientFactory" class="com.example.solr.AuthHttpSolrClientFactory" scope="singleton">
    <constructor-arg ref="solrClient" />
    <constructor-arg name="core">
        <null />
    </constructor-arg>
    <constructor-arg ref="credentials" />
    <constructor-arg type="java.lang.String" value="BASIC"/>
</bean>
<bean id="solrTemplate" class="org.springframework.data.solr.core.SolrTemplate" scope="singleton">
    <constructor-arg ref="solrClientFactory" />
    <constructor-arg name="requestMethod">
        <value type="org.springframework.data.solr.core.RequestMethod">POST</value>
    </constructor-arg>
</bean>
Fei
  • 81
  • 1
  • 2
0

There are several reasons why your code is not working, but they are lied mainly to spring-data-solr missing features.

First of all spring-data-solr version 2.0.4 does not have support for Solr 5 (cloud) features. So this is the reason why you are getting the NullPointerException in the method org.springframework.data.solr.server.support.SolrClientUtils#cloneLBHttpSolrClient

I've tried to see whether it the scenario exposed by you works with the latest SNAPSHOT (2.1.0-SNAPSHOT) of spring-data-solr and after a few modifications on the SolrContext spring configuration class :

@Configuration
@EnableSolrRepositories(basePackages = {"com.acme.solr"})
// notice that the multicoresupport is left false
// see org.springframework.data.solr.repository.config.SolrRepositoryConfigExtension#postProcess(org.springframework.beans.factory.support.BeanDefinitionBuilder, org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource) for details
public class SolrContext {

    @Bean
    public Credentials credentials(@Value("${solr.username}") String username, @Value("${solr.password}") String
            password) {
        return new UsernamePasswordCredentials(username, password);
    }

    @Bean
    public BasicCredentialsProvider credentialsProvider(Credentials credentials) {
        BasicCredentialsProvider provider = new BasicCredentialsProvider();
        provider.setCredentials(AuthScope.ANY, credentials);

        return provider;
    }

    @Bean
    public SolrClientFactory solrClientFactory(SolrClient solrClient, Credentials credentials) {
        return new HttpSolrClientFactory(solrClient, "", credentials, "BASIC");

    }

    // create a solrtemplate bean, so that it is used in
    // org.springframework.data.solr.repository.support.SolrRepositoryFactoryBean#doCreateRepositoryFactory method
    // for using org.springframework.data.solr.repository.support.SolrRepositoryFactory#SolrRepositoryFactory(org.springframework.data.solr.core.SolrOperations) constructor
    @Bean
    public SolrTemplate solrTemplate(SolrClientFactory solrClientFactory){
        return new SolrTemplate(solrClientFactory);
    }

    @Bean
    public CloudSolrClient solrClient(@Value("${zkHost}") String zkHost) {
        CloudSolrClient solrClient = new CloudSolrClient.Builder().withZkHost(zkHost).build();
        solrClient.setDefaultCollection("gettingstarted");
        return solrClient;
    }
}

I still received a 401 authentication issue when performing a solr request (when the basic authentication was enabled on solr).

In a vanilla solrj application, this is how you'd make an authenticated request:

    CloudSolrClient solr = new CloudSolrClient.Builder()
            .withZkHost("localhost:9983")
            .build();

    SolrQuery query = new SolrQuery();
    query.setQuery("*:*");
    SolrRequest<QueryResponse> req = new QueryRequest(query);
    req.setBasicAuthCredentials("solr", "SolrRocks");
    QueryResponse rsp = req.process(solr, "gettingstarted");

    System.out.println("numFound: " + rsp.getResults().getNumFound());

When looking whether the method SolrRequest#setBasicAuthCredentials(String, String) is used in the code of spring-data-solr I didn't notice anywhere this method being used. So, it is very likely, that this feature is not implemented yet not even on the SNAPSHOT build of the spring-data-solr.

I've created a feature request on spring-data-solr project to add support for this functionality.

marius_neo
  • 1,535
  • 1
  • 13
  • 28
0

Yes, your configuration seems incorrect. I had same issue like yours

I would like to use apache solr version 6.6.0 and spring data solr version 2.0.8 (bought by spring boot starter). It turned out that the version from spring data solr doesn't have support for apache solr version > 5, because when you traced

at org.springframework.data.solr.core.SolrTemplate.execute(SolrTemplate.java:172), it's very clear that when the solrTemplate is going to createClientForCore, it will clone from the cloudSolrClient that we have been configured

enter image description here

the problem is in String zkHost = (String)readField(solrClient, "zkHost"); * it will return null since in apache solr version > 5, the zkHost is stored in "clusterStateProvider" not in the same level as "cloudSolrClient"

Solved: If you want keep using spring data solr version 2, you need to downgrade the apache solr version

avelox
  • 66
  • 4
0

Had the same issue usind Solr 7.7 and spring-boot-starter-data-solr 2.1.14 (uses spring-data-solr-4.0.17.RELEASE)

Tried a few ways, including creating custom HttpSolrClientFactory. It works but actually makes 2 calls to Solr and the first one returns 401 Unauthorized.

I fix the problem extending the CloudSolrClient (trying to do correct auth as it described in Basic Auth with SolrJ)

It makes only one call to Solr and uses Basic auth

public class BasicAuthCloudSolrClient extends CloudSolrClient {

private final Credentials credentials;

/**
 * Create a new client object that connects to Zookeeper using BASIC Authentication and is always aware
 * of the SolrCloud state. If there is a fully redundant Zookeeper quorum and
 * SolrCloud has enough replicas for every shard in a collection, there is no
 * single point of failure. Updates will be sent to shard leaders by default.
 *
 * @param builder a {@link BasicAuthCloudSolrClient.Builder} with the options used to create the client.
 */
protected BasicAuthCloudSolrClient(Builder builder) {
    super(builder);
    this.credentials = builder.credentials;
}

@Override
public QueryResponse query(String collection, SolrParams params, SolrRequest.METHOD method)
        throws
        SolrServerException, IOException {
    QueryRequest request = new QueryRequest(params, method);
    request.setBasicAuthCredentials(credentials.getUserPrincipal().getName(),
            credentials.getPassword());

    return request.process(this, collection);
}


/**
 * Constructs {@link BasicAuthCloudSolrClient} instances from provided configuration.
 */
public static class Builder extends CloudSolrClient.Builder {
    protected Credentials credentials;

    /**
     * @deprecated use other constructors instead.  This constructor will be changing visibility in an upcoming release.
     */
    @Deprecated
    public Builder() {
    }

    /**
     * Provide a series of ZK hosts which will be used when configuring {@link CloudSolrClient} instances.
     *
     * @param zkHosts     a List of at least one ZooKeeper host and port (e.g. "zookeeper1:2181")
     * @param credentials a credentials to connect to Solr.
     */
    public Builder(List<String> zkHosts, Credentials credentials) {
        super(zkHosts, empty());
        this.credentials = credentials;
    }


    /**
     * Create a {@link CloudSolrClient} based on the provided configuration.
     */
    public BasicAuthCloudSolrClient build() {

        if (Objects.isNull(credentials)) {
            throw new IllegalArgumentException(
                    "Credentials must be provided to initialize BasicAuthCloudSolrClient");
        }
        if (stateProvider == null) {
            if (!zkHosts.isEmpty()) {
                stateProvider = new ZkClientClusterStateProvider(zkHosts, zkChroot);
            } else if (!this.solrUrls.isEmpty()) {
                try {
                    stateProvider = new HttpClusterStateProvider(solrUrls, httpClient);
                } catch (Exception e) {
                    throw new RuntimeException(
                            "Couldn't initialize a HttpClusterStateProvider (is/are the "
                                    + "Solr server(s), " + solrUrls + ", down?)", e);
                }
            } else {
                throw new IllegalArgumentException("Both zkHosts and solrUrl cannot be null.");
            }
        }
        return new BasicAuthCloudSolrClient(this);
    }

    @Override
    public BasicAuthCloudSolrClient.Builder getThis() {
        return this;
    }
}

Configuration looks like this:

@Bean
public Credentials solrCredentials(@Value("${solr.username}") String username, @Value("${solr.password}") String
        password) {
    return new UsernamePasswordCredentials(username, password);
}


@Bean
public SolrClientFactory solrClientFactory(SolrClient solrClient,
        Credentials solrCredentials) {
    return new HttpSolrClientFactory(solrClient, solrCredentials, AuthSchemes.BASIC);
}

@Bean
public SolrTemplate solrTemplate(SolrClientFactory solrClientFactory){
    return new SolrTemplate(solrClientFactory);
}

@Bean
public SolrClient solrClient(Credentials solrCredentials) {
    if (isNotEmpty(properties.getZkHosts())) {

        BasicAuthCloudSolrClient solrClient =
                new BasicAuthCloudSolrClient.Builder(properties.getZkHosts(), solrCredentials).build();
        solrClient.setDefaultCollection(properties.getCollection());
        return solrClient;
    } else {
        throw new IllegalStateException("ZkHosts is required for application startup.");
    }
}