2
votes

I am trying to write a sample in order to learn couchbase. I am trying to use it with spring boot and it’s crud repositories .

So I have downloaded latest docker image but the point is: i could not find the password of the bucket. The couchbase console allows only user creation but in spring, there is no equivalent of this usage like a username/password. It allows only bucketName and password which does not seem compatible with couchbase 5.

Am I missing anything here or is spring not compatible with couchbase 5? If spring is not compatible, which version of couchbase is ok?

Thx

3

3 Answers

7
votes

Spring Data Couchbase is compatible with Couchbase Server 5.0. You can achieve the same auth as 4.x by creating a user with the same name as the bucket, then just use that bucket name and password from Spring Data if it's prior to 3.0/Kay.

The docs should cover this and if there's anything confusing there, please click the "feedback" button and offer what could be improved!

https://developer.couchbase.com/documentation/server/5.0/security/security-authorization.html https://developer.couchbase.com/documentation/server/5.0/security/concepts-rba-for-apps.html https://developer.couchbase.com/documentation/server/5.0/security/security-resources-under-access-control.html

3
votes

I faced the same issue. I started debugging by getting into AbstractCouchbaseConfiguration and there i found

public abstract class AbstractCouchbaseConfiguration
    extends AbstractCouchbaseDataConfiguration implements CouchbaseConfigurer {

  ....//some other configuration


@Override
@Bean(name = BeanNames.COUCHBASE_CLUSTER_INFO)
public ClusterInfo couchbaseClusterInfo() throws Exception {
  return couchbaseCluster().clusterManager(getBucketName(), getBucketPassword()).info();
}

What i did is created a bucket with the same name as of my couchbase user.

couchbase username : userdetail

couchbase password : ******

bucket name : userdetail

2
votes

Couchbase driver supports connection to Couchbase 5 buckets using username/password. Problem is that spring-data-couchbase is not developed fast enough to cover all the new features Couchbase introduces. So, we need to help Spring to use a new bucket connection, doing it by overriding Couchbase cluster instantiation method of spring-data-couchbase configuration base class - org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration. This is the method we are looking at :

@Override
@Bean(name = BeanNames.COUCHBASE_CLUSTER_INFO)
public ClusterInfo couchbaseClusterInfo() throws Exception {
return couchbaseCluster().clusterManager(getBucketName(), getBucketPassword()).info();
}

as we can see, it's not using username, just bucket and password, so in our configuration we will override it as following :

@Override
@Bean(name = BeanNames.COUCHBASE_CLUSTER_INFO)
public ClusterInfo couchbaseClusterInfo() throws Exception {
return couchbaseCluster().authenticate(couchbaseUsername, couchbasePassword).clusterManager().info();
}

that's it. Here is the full code of my spring-data-couchbase configuration :

import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.cluster.ClusterInfo;
import com.couchbase.client.java.env.CouchbaseEnvironment;
import com.couchbase.client.java.env.DefaultCouchbaseEnvironment;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
import org.springframework.data.couchbase.config.BeanNames;
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;

import javax.inject.Inject;
import java.security.KeyStore;
import java.util.List;

/**
 * @author by avoinovan
 */
@Configuration
@EnableCouchbaseRepositories
public class ModelConfig extends AbstractCouchbaseConfiguration {

    private final static int DEFAULT_HTTP_PORT = 8091;
    private final static int DEFAULT_HTTP_SSL_PORT = 18091;
    private final static int DEFAULT_CARRIER_PORT = 11210;
    private final static int DEFAULT_CARRIER_SSL_PORT = 11207;

    private final static long DEFAULT_KEEP_ALIVE_INTERVAL = 30000;
    private final static int DEFAULT_SOCKET_CONNECT_TIMEOUT_MS = 5000;
    private final static long DEFAULT_CONNECT_TIMEOUT_MS = 5000;
    private final static long DEFAULT_MANAGEMENT_TIMEOUT_MS = 75000;
    private final static long DEFAULT_DISCONNECT_TIMEOUT_MS = 25000;

    private final static String PROPERTY_KEEP_ALIVE_INTERVAL_MS = "couchbase.keep_alive_interval_ms";

    private final static String PROPERTY_SOCKET_CONNECT_TIMEOUT_MS = "couchbase.socket_connect_timeout_ms";
    private final static String PROPERTY_CONNECT_TIMEOUT_MS = "couchbase.connect_timeout_ms";
    private final static String PROPERTY_MANAGEMENT_TIMEOUT_MS = "couchbase.management_timeout_ms";
    private final static String PROPERTY_DISCONNECT_TIMEOUT_MS = "couchbase.disconnect_timeout_ms";

    private final static String PROPERTY_SSL_ENABLED = "couchbase.ssl.enabled";
    private final static String PROPERTY_SSL_KEYSTORE_FILE = "couchbase.ssl.keystore.file";
    private final static String PROPERTY_SSL_KEYSTORE_PASSWORD = "couchbase.ssl.keystore.password";
    private final static String PROPERTY_SSL_TRUSTSTORE_FILE = "couchbase.ssl.truststore.file";
    private final static String PROPERTY_SSL_TRUSTSTORE_PASSWORD = "couchbase.ssl.truststore.password";

    private final static String PROPERTY_BOOTSTRAP_HTTP_ENABLED = "couchbase.bootstrap.http.enabled";
    private final static String PROPERTY_BOOTSTRAP_HTTP_PORT = "couchbase.bootstrap.http.port";
    private final static String PROPERTY_BOOTSTRAP_HTTP_SSL_PORT = "couchbase.bootstrap.http.ssl.port";
    private final static String PROPERTY_BOOTSTRAP_CARRIER_ENABLED = "couchbase.bootstrap.carrier.enabled";
    private final static String PROPERTY_BOOTSTRAP_CARRIER_PORT = "couchbase.bootstrap.carrier.port";
    private final static String PROPERTY_BOOTSTRAP_CARRIER_SSL_PORT = "couchbase.bootstrap.carrier.ssl.port";

    @Value("#{'${spring.couchbase.bootstrap-hosts}'.split(',')}")
    private List<String> couchbaseBootstrapHosts;

    @Value("${spring.couchbase.bucket.name}")
    private String bucketName;

    @Value("${spring.couchbase.password}")
    private String couchbasePassword;

    @Value("${spring.couchbase.username}")
    private String couchbaseUsername;

    private final Environment environment;

    private final ResourceLoader resourceLoader;

    @Inject
    public ModelConfig(final Environment environment,
                       final ResourceLoader resourceLoader) {
        this.environment = environment;
        this.resourceLoader = resourceLoader;
    }

    protected List<String> getBootstrapHosts() {
        return couchbaseBootstrapHosts;
    }

    protected String getBucketName() {
        return bucketName;
    }

    protected String getBucketPassword() {
        return couchbasePassword;
    }

    protected CouchbaseEnvironment getEnvironment() {
        return DefaultCouchbaseEnvironment.builder()
                .keepAliveInterval(environment.getProperty(PROPERTY_KEEP_ALIVE_INTERVAL_MS,
                        Long.class,
                        DEFAULT_KEEP_ALIVE_INTERVAL))

                // timeout settings
                .socketConnectTimeout(environment.getProperty(PROPERTY_SOCKET_CONNECT_TIMEOUT_MS,
                        Integer.class,
                        DEFAULT_SOCKET_CONNECT_TIMEOUT_MS))
                .connectTimeout(environment.getProperty(PROPERTY_CONNECT_TIMEOUT_MS,
                        Long.class,
                        DEFAULT_CONNECT_TIMEOUT_MS))
                .managementTimeout(environment.getProperty(PROPERTY_MANAGEMENT_TIMEOUT_MS,
                        Long.class,
                        DEFAULT_MANAGEMENT_TIMEOUT_MS))
                .disconnectTimeout(environment.getProperty(PROPERTY_DISCONNECT_TIMEOUT_MS,
                        Long.class,
                        DEFAULT_DISCONNECT_TIMEOUT_MS))

                // port and ssl
                .sslEnabled(environment.getProperty(PROPERTY_SSL_ENABLED, Boolean.class, false))
                .bootstrapHttpEnabled(environment.getProperty(PROPERTY_BOOTSTRAP_HTTP_ENABLED,
                        Boolean.class,
                        Boolean.TRUE))
                .bootstrapHttpDirectPort(environment.getProperty(PROPERTY_BOOTSTRAP_HTTP_PORT,
                        Integer.class,
                        DEFAULT_HTTP_PORT))
                .bootstrapHttpSslPort(environment.getProperty(PROPERTY_BOOTSTRAP_HTTP_SSL_PORT,
                        Integer.class,
                        DEFAULT_HTTP_SSL_PORT))
                .bootstrapCarrierEnabled(environment.getProperty(PROPERTY_BOOTSTRAP_CARRIER_ENABLED,
                        Boolean.class,
                        Boolean.TRUE))
                .bootstrapCarrierDirectPort(environment.getProperty(PROPERTY_BOOTSTRAP_CARRIER_PORT,
                        Integer.class,
                        DEFAULT_CARRIER_PORT))
                .bootstrapCarrierSslPort(environment.getProperty(PROPERTY_BOOTSTRAP_CARRIER_SSL_PORT,
                        Integer.class,
                        DEFAULT_CARRIER_SSL_PORT))

                // keystore and trust store
                .sslKeystore(createKeyStore(environment, resourceLoader))
                .sslTruststore(createTrustStore(environment, resourceLoader))

                .build();
    }

    @Override
    @Bean(name = BeanNames.COUCHBASE_CLUSTER_INFO)
    public ClusterInfo couchbaseClusterInfo() throws Exception {
        return couchbaseCluster().authenticate(couchbaseUsername, couchbasePassword).clusterManager().info();
    }

    /**
     * Return the {@link Bucket} instance to connect to.
     *
     * @throws Exception on Bean construction failure.
     */
    @Override
    @Bean(destroyMethod = "close", name = BeanNames.COUCHBASE_BUCKET)
    public Bucket couchbaseClient() throws Exception {
        //@Bean method can use another @Bean method in the same @Configuration by directly invoking it
        return couchbaseCluster().openBucket(getBucketName());
    }

    private KeyStore createKeyStore(final Environment environment, final ResourceLoader resourceLoader) {
        return loadKeyStore(environment, resourceLoader, PROPERTY_SSL_KEYSTORE_FILE, PROPERTY_SSL_KEYSTORE_PASSWORD);
    }

    private KeyStore createTrustStore(final Environment environment, final ResourceLoader resourceLoader) {
        return loadKeyStore(environment, resourceLoader, PROPERTY_SSL_TRUSTSTORE_FILE, PROPERTY_SSL_TRUSTSTORE_PASSWORD);
    }

    private KeyStore loadKeyStore(final Environment environment,
                                  final ResourceLoader resourceLoader,
                                  final String fileProperty,
                                  final String passwordProperty) {
        String file = environment.getProperty(fileProperty);
        String password = environment.getProperty(passwordProperty);

        if (file != null) {
            Resource resource = resourceLoader.getResource(file);
            if (resource != null) {
                try {
                    KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
                    keyStore.load(resource.getInputStream(), password == null ? null : password.toCharArray());
                    return keyStore;
                } catch (final Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
        return null;
    }
}