I use Spring Data LDAP and Spring Boot provides out of the box support for an embedded UnboundID server. However, when I use Spring Data LDAP's #Entry annotation, I need to specify a different base in the annotation based on whether I'm using the embedded UnboundID LDAP server, or a remote Active Directory server.
I was attempting to do this with SpEL and profile-based properties by specifying:
#Entry(base = "${ldap.person.base}", ...)
Then I have an application.propreties with ldap.person.base=OU=AD Person Base and an application-embedded.properties with ldap.person.base=OU=Embedded Person Base.
However, the #Entry annotation does not seem to support SpEL evaluation:
javax.naming.InvalidNameException: Invalid name: ${ldap.person.base}
There is an open issue in Spring LDAP to add support for this, but is there any workaround or some other way I can accomplish this until it is supported in Spring LDAP?
I'm not sure I'm following here, but assuming you're using the LDAP auto-configuration in Spring Boot, is it not enough to set the property spring.ldap.base to one or the other (OU=AD Person Base or OU=Embedded Person Base) based on the profile you're using?
Both EmbeddedLdapAutoConfiguration and LdapAutoConfiguration use an LdapProperties object to set various attributes on the LdapContextSource during bean creation, including its base. As far as I can tell, you won't have to define it for each #Entry in your codebase if LdapContextSource.base is set.
If you're not using the auto-configuration, and if I'm correct in my assumptions, you should still be able to create your own LdapContextSource bean and set its base to the desired value based on a Spring property.
Turns out the reason I needed a different base in the first place is because Spring was not setting the base on the ContextSource.
When you let Spring Boot autoconfigure the embedded LDAP server, it creates a ContextSource as such in EmbeddedLdapAutoConfiguration:
#Bean
#DependsOn("directoryServer")
#ConditionalOnMissingBean
public ContextSource ldapContextSource() {
LdapContextSource source = new LdapContextSource();
if (hasCredentials(this.embeddedProperties.getCredential())) {
source.setUserDn(this.embeddedProperties.getCredential().getUsername());
source.setPassword(this.embeddedProperties.getCredential().getPassword());
}
source.setUrls(this.properties.determineUrls(this.environment));
return source;
}
As you can see, nowhere in there does it call source.setBase(). So to solve this, I added a configuration file with #Profile("embedded") and manually created a ContextSource where I set the base myself (I leave off the credentials part because I don't use credentials for the embedded server):
#Configuration
#Profile("embedded")
#EnableConfigurationProperties({ LdapProperties.class })
public class EmbeddedLdapConfig {
private final Environment environment;
private final LdapProperties properties;
public EmbeddedLdapConfig(final Environment environment, final LdapProperties properties) {
this.environment = environment;
this.properties = properties;
}
#Bean
#DependsOn("directoryServer")
public ContextSource ldapContextSource() {
final LdapContextSource source = new LdapContextSource();
source.setUrls(this.properties.determineUrls(this.environment));
source.setBase(this.properties.getBase());
return source;
}
}
Now, I can leave the value of the base attribute in my #Entry the same for both the Active Directory server and the embedded UnboundID server and it works properly.
Related
To test ma LDAP service. I set up the embedded LDAP config like that:
spring:
ldap:
base: OU=Internals,DC=int,DC=springboot,DC=dev
username: uid=admin
password: secret
urls: ldap://localhost:8389/
embedded:
base-dn: DC=springboot,DC=dev
credential:
username: uid=admin
password: secret
ldif: classpath:export2-ldap.ldif
port: 8389
validation:
enabled: false
I notice that the ldaptemplate base is not correctly set:
I've dug into the EmbeddedLdapAutoConfiguration and LdapAutoConfiguration code, and I've noticed that the EmbeddedLdapAutoConfiguration creates a bean LdapContextSource, without the base, before the LdapAutoConfiguration class.
#Configuration(proxyBeanMethods = false)
#ConditionalOnClass(ContextSource.class)
static class EmbeddedLdapContextConfiguration {
#Bean
#DependsOn("directoryServer")
#ConditionalOnMissingBean
LdapContextSource ldapContextSource(Environment environment, LdapProperties properties,
EmbeddedLdapProperties embeddedProperties) {
LdapContextSource source = new LdapContextSource();
if (embeddedProperties.getCredential().isAvailable()) {
source.setUserDn(embeddedProperties.getCredential().getUsername());
source.setPassword(embeddedProperties.getCredential().getPassword());
}
source.setUrls(properties.determineUrls(environment));
return source;
}
}
Is it normal, is not possible to use both spring.ldap.base and spring.ldap.embedded.* ? Or maybe something is not correctly set in my projet.
I got around this with the following:
#Bean
public LdapContextSource createLdapConfig(LdapProperties properties, Environment environment,
ObjectProvider<DirContextAuthenticationStrategy> dirContextAuthenticationStrategy) {
LdapAutoConfiguration config = new LdapAutoConfiguration();
return config.ldapContextSource(properties, environment, dirContextAuthenticationStrategy);
}
As #Saikat noted, it appears both spring, and the unboundid embedded LDAP server are configured to create a LdapContextSource if it doesn't exist already... Sounds like the embedded LDAP server is winning the race, and screwing things up for everyone else.
The above code gets around the problem by just forcing the creation / configuration of a LdapContextSource, and thus not letting Spring nor the embedded ldap server try to create a LdapContextSource.
I'm new in Spring applications, and see the big difference between configurations in springBoot and spring. So my questin is: apart from spring-boot, is there a way to setup a proper spring application(with web mvc, security, aop, ...), without any xml config file (ie : config relying only on annotations).
Yes, there is a way to do this in Spring. Spring Boot is after all an enhanced, autoconfigured Spring (with other cool features). That means that everything there is in Spring Boot should be achievable in Spring as well, but you would have do a bit/a lot of Your own extra work.
Moving straight to the point, in order to achieve what you want, you would need to undertake the following steps:
Create a class, which will store all the configuration (basically the properties you would store in the xml file) - let's call it AppConfig.class
Annotate the AppConfig.class with #Configuration - this will inform Spring that this class is the source of configuration;
Annotate the AppConfig.class with #ComponentScan("com.app") - here, You need to provide a package, from which Spring has to start component scanning in order to find Beans to be registered in Spring Container. Important note is, that it will scan the package and it's subpackages, so you would mostly want to provide here the top level package;
If you need some data to be injected into your beans, you would want to use the #PropertySource("classpath:application.properties") - I have provided here the default value, which Spring Boot uses internally in case you want to inject some data into your beans at runtime. For this to work, you need to inject into AppConfig.class an Environment.class
To show it on the example:
#Configuration
#ComponentScan("com.app")
#PropertySource("classpath:application.properties")
public class AppConfig {
// it will help to pull the properties incorporated in the file you have provided in the #PropertySource annotation
private Environment environment;
//inject it
public AppConfig(Environment environment) {
this.environment = environment;
}
// build your beans - the getProperty method accepts the key from application.properties
// file and return a value as a String. You can provide additional arguments to convert
//the value and a default value if the property is not found
#Bean
public Product product() {
return new Product(
environment.getProperty("product.name", "XXX"),
environment.getProperty("product.price", BigDecimal.class, BigDecimal.ZERO),
environment.getProperty("product.quantity", Integer.class, 10)
);
}
}
I hope that it helps
I have mysql database and i have configured database properties in application.properties file .
Now if i do change db connection properties , i want reflect that changes into my application on the fly , means with out restarting the server
Is this possible using with spring cloud config server and actuator?
I have tested this quite a bit and here are my findings.
Spring config server works pretty well for simple key value pairs.
It also works for Database properties provided that you are creating datasource objects yourself and you are using #RefreshScope.
For example, if you have a config server with these properties.
mongodb.feed.database=kiran
mongodb.feed.host=localhost
mongodb.feed.port=27017
And you are configuring MongoTemplate in your application like this.
#Configuration
#ConfigurationProperties(prefix = "mongodb.feed")
#EnableMongoRepositories(basePackages = "in.phani.springboot.repository", mongoTemplateRef = "feedMongoTemplate")
#Setter
class FeedMongoConfig {
private String host;
private int port;
private String database;
#Primary
#Bean(name = "feedMongoTemplate")
#RefreshScope // this is the key
public MongoTemplate feedMongoTemplate() throws Exception {
final Mongo mongoClient = createMongoClient(new ServerAddress(host, port));
return new MongoTemplate(mongoClient, database);
}
Mongo createMongoClient(ServerAddress serverAddress) {
return new MongoClient(serverAddress);
}
}
And if you change the database name in your config properties and then refresh the scope with /refresh endpoint. It works pretty well.
With springboot you need not do manual configuration like this. Spring boot has Autoconfiguration for most of the stuff. Continuing with the same example above, if you were to put in config properties something like this
spring.data.mongodb.uri=mongodb://localhost:27017/phani
spring-boot will configure MongoTemplate for you(you don't need to create yourself as in 2nd point).
Here comes the hiccup.
Now if you change the database name, and refresh the scope, it doesn't work. Because in this case, MongoTemplate was configured by spring-boot Autoconfiguration(MongoAutoConfiguration)
So in conclusion, it needs extensive testing to be done, before using it on production(especially for complex beans like datasources, MongoTemplates), since there is not enough documentation on this.. But I would say, it is worth trying.
I have multitenant database in Spring Boot. I store multi spring JDBC templates (based on tomcat Data Sources, configured manually) in map (immutable bean). And I choose proper data source based on uuid in a request (connection pool per database). I have disabled standard configuration in Spring Boot by:
#SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
What is the proper way of transaction manager configuration? With single data source I can use PlatformTransactionManager, but how it should be done with multiple jdbc templates/data sources in spring? It would be the best if I could set everything dynamically. Thanks in advance.
Here a solution for using multiple datasources
http://www.baeldung.com/spring-data-jpa-multiple-databases
Configure Two DataSources
If you need to configure multiple data sources, you can apply the same tricks that are described in the previous section. You must, however, mark one of the DataSource #Primary as various auto-configurations down the road expect to be able to get one by type.
If you create your own DataSource, the auto-configuration will back off. In the example below, we provide the exact same features set than what the auto-configuration provides on the primary data source
#Bean
#Primary
#ConfigurationProperties("app.datasource.foo")
public DataSourceProperties fooDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#Primary
#ConfigurationProperties("app.datasource.foo")
public DataSource fooDataSource() {
return fooDataSourceProperties().initializeDataSourceBuilder().build();
}
#Bean
#ConfigurationProperties("app.datasource.bar")
public BasicDataSource barDataSource() {
return (BasicDataSource) DataSourceBuilder.create()
.type(BasicDataSource.class).build();
}
fooDataSourceProperties has to be flagged #Primary so that the database initializer feature uses your copy (should you use that).
app.datasource.foo.type=com.zaxxer.hikari.HikariDataSource
app.datasource.foo.maximum-pool-size=30
app.datasource.bar.url=jdbc:mysql://localhost/test
app.datasource.bar.username=dbuser
app.datasource.bar.password=dbpass
app.datasource.bar.max-total=30
I have an authentication service that I want to autoconfigure at runtime but that will be mocked up for development and testing. I would like to use the #ConfigurationProperties feature to define the necessary parameters, but I also need to be able to only conditionally create the AuthenticationManager instances, depending on whether a live service is configured.
The approach I would like to take is to use something like #ConditionalOnBean(AuthProperties.class), but
Spring Boot creates a bean of my #ConfigurationProperties class regardless of whether the properties are present. I can apply validation annotations to the fields, but then the context won't start at all if a live service is not configured.
Is there a clean way to make a configuration section conditional on having the properties specified in an #ConfigurationProperties class without repeating the property names in #ConditionalOnProperty?
Can't you just use profile of your application-some_profile.properties file and then create your properties bean conditionally like this
#Profile("some_profile")
#Component
#ConfigurationProperties(prefix = "some.selector")
public Class YourConfiguration{
private property option;
}
this way it only gets created conditionally depending which profile you select live or mock.
Than you can either use selected profile for your #Configuration authentication classes for separate approaches or follow with #ConditionalOnBean. That's what profiles are for or maybe I misunderstood the question.
I had a similar problem and I could resolve it with this approach using Spring Boot and Java 8, hope it helps someone:
#Configuration
#PropertySources({
#PropertySource("classpath:integration.properties"),
#PropertySource(value = "classpath:integration-${spring.profiles.active}.properties", ignoreResourceNotFound = true)
})
#ConfigurationProperties(prefix = "integration")
public class IntegrationProperties {
private String user;
private String password;
private String projectId;
....
}
integration.properties
integration.user=User
integration.password=Password
integration.project-id=123
integration-prod.properties
integration.project-id=897
Spring Boot will create the bean using the properties file, and for production (using the profile prod) it will use the other file and override the project-id. The ignoreResourceNotFound = true will avoid the FileNotFoundException on startup when you are not using the prod profile.