Spring Boot Security/LDAP direct bind with user credentials - spring-boot

Is there a way to authenticate a user with Spring Boot Security/LDAP using the credentials instead of first binding with some functional credentials and then trying to bind the user?
I want to not need to use managerDn and managerPassword like in:
auth.ldapAuthentication()
.userSearchBase("")
.userSearchFilter("(samAccountName={0})")
.contextSource()
.url("ldap://<url>/<root>")
.managerDn("functionUser")
.managerPassword("password")

In my application, I implemented a custom UsernamePasswordAuthenticationProvider to authenticate a user against my own database or a remote LDAP depending on a flag set in the user record.
To authenticate against the remote LDAP, I used the code below. It worked for me, perhaps, it will work for you too :).
protected void validateCredentialsAgainstActiveDirectory(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) {
try {
LdapConfiguration config = ...;
/*
* We will create a new LDAP connection on the fly each time an AD user logs in.
* Hence we must disable caching properties to avoid NullPointerException later
* in AbstractContextSource.getAuthenticatedEnv().
*
*/
DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(config.getUrl());
contextSource.setCacheEnvironmentProperties(false);
// Authenticate the user against the pre-configured userDnTemplate
BindAuthenticator bindAuthenticator = new BindAuthenticator(contextSource);
bindAuthenticator.setUserDnPatterns(new String[] { config.getUserDnTemplate() });
bindAuthenticator.authenticate(authentication);
} catch (BadCredentialsException ex) {
// Catch downstream exception to return our own message
throw new BadCredentialsException(SpringUtils.getMessage("security.login.error.bad-credentials"));
}
}
FYI, LdapConfiguration is my own custom class for reading configurations from a .yml file. In this file, I configured the url and the DN template of the LDAP server as following. You need to change that to fit your environment.
url: ldap://10.10.10.231:10389/dc=mycompany,dc=com
userDnTemplate: uid={0},ou=people
Don't forget to import the necessary dependencies in your project too.
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-ldap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-core</artifactId>
</dependency>

Related

Micrometer with Prometheus Pushgateway - Add TLS Support

I have a Spring boot application with Prometheus Pushgateway using Micrometer, mainly based on this tutorial:
https://luramarchanjo.tech/2020/01/05/spring-boot-2.2-and-prometheus-pushgateway-with-micrometer.html
pom.xml has following related dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>simpleclient_pushgateway</artifactId>
<version>0.16.0</version>
</dependency>
And application.properties file has:
management.metrics.export.prometheus.pushgateway.enabled=true
management.metrics.export.prometheus.pushgateway.shutdown-operation=PUSH
management.metrics.export.prometheus.pushgateway.baseUrl=localhost:9091
It is working fine locally in Dev environment while connecting to Pushgateway without any TLS. In our CI environment, Prometheus Pushgateway has TLS enabled. How do I configure TLS support and configure certs in this Spring boot application?
Due to the usage of TLS, you will need to customize a few Spring classes:
HttpConnectionFactory -> PushGateway -> PrometheusPushGatewayManager
A HttpConnectionFactory, is used by prometheus' PushGateway to create a secure connection, and then, create a PrometheusPushGatewayManager which uses the previous pushgateway.
You will need to implement the prometheus’ interface HttpConnectionFactory, I’m assuming you are able to create a valid javax.net.ssl.SSLContext object (if not, more details in the end¹).
HttpConnectionFactory example:
public class MyTlsConnectionFactory implements io.prometheus.client.exporter.HttpConnectionFactory {
#Override
public HttpURLConnection create(String hostUrl) {
// considering you can get javax.net.ssl.SSLContext or javax.net.ssl.SSLSocketFactory
URL url = new URL(hostUrl);
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setSSLSocketFactory(sslContext.getSocketFactory());
return connection;
}
}
PushGateway and PrometheusPushGatewayManager:
#Bean
public HttpConnectionFactory tlsConnectionFactory() {
return new MyTlsConnectionFactory();
}
#Bean
public PushGateway pushGateway(HttpConnectionFactory connectionFactory) throws MalformedURLException {
String url = "https://localhost:9091"; // replace by your props
PushGateway pushGateway = new PushGateway(new URL(url));
pushGateway.setConnectionFactory(connectionFactory);
return pushGateway;
}
#Bean
public PrometheusPushGatewayManager tlsPrometheusPushGatewayManager(PushGateway pushGateway,
CollectorRegistry registry) {
// fill the others params accordingly (the important is pushGateway!)
return new PrometheusPushGatewayManager(
pushGateway,
registry,
Duration.of(15, ChronoUnit.SECONDS),
"some-job-id",
null,
PrometheusPushGatewayManager.ShutdownOperation.PUSH
);
}
¹If you face difficulty retrieving the SSLContext from java code, I recommend studying the library https://github.com/Hakky54/sslcontext-kickstart and https://github.com/Hakky54/mutual-tls-ssl (which shows how to apply it with different client libs).
Then, will be possible to generate SSLContext in java code in a clean way, e.g.:
String keyStorePath = "client.jks";
char[] keyStorePassword = "password".toCharArray();
SSLFactory sslFactory = SSLFactory.builder()
.withIdentityMaterial(keyStorePath, keyStorePassword)
.build();
javax.net.ssl.SSLContext sslContext = sslFactory.getSslContext();
Finally, if you need setup a local Prometheus + TLS environment for testing purposes, I recommend following the post:
https://smallstep.com/hello-mtls/doc/client/prometheus

Problem with kerberos iis authentication in spring boot application on windows

i'a trying to deploy my jar spring boot application on windows but get error : [Krb5LoginModule] authentication failed
KrbException: Cannot locate default realm
In my localhost, everything is OK with the authentication but whene i deploy the jar in the production server i got the error even if both windows are in the same campany doamin.
the system administrator told me that for other application, the authentication is based on Kerberos and iis so the ticket exchange for authentication is very easy.
Here's my security config :
#Bean
public KerberosAuthenticationProvider kerberosAuthenticationProvider() {
KerberosAuthenticationProvider provider =
new KerberosAuthenticationProvider();
SunJaasKerberosClient client = new SunJaasKerberosClient();
client.setDebug(true);
provider.setKerberosClient(client);
provider.setUserDetailsService(dummyUserDetailsService());
return provider;
}
#Bean
public SpnegoEntryPoint spnegoEntryPoint() {
//return new SpnegoEntryPoint("/login");
return new SpnegoEntryPoint();
}
#Bean
public SpnegoAuthenticationProcessingFilter spnegoAuthenticationProcessingFilter(
AuthenticationManager authenticationManager) {
SpnegoAuthenticationProcessingFilter filter =
new SpnegoAuthenticationProcessingFilter();
filter.setAuthenticationManager(authenticationManager);
return filter;
}
#Bean
public KerberosServiceAuthenticationProvider kerberosServiceAuthenticationProvider() {
KerberosServiceAuthenticationProvider provider =
new KerberosServiceAuthenticationProvider();
provider.setTicketValidator(sunJaasKerberosTicketValidator());
provider.setUserDetailsService(dummyUserDetailsService());
return provider;
}
#Bean
public SunJaasKerberosTicketValidator sunJaasKerberosTicketValidator() {
SunJaasKerberosTicketValidator ticketValidator =
new SunJaasKerberosTicketValidator();
ticketValidator.setServicePrincipal("HTTP/localhost#fgao.fr");
ticketValidator.setKeyTabLocation(new
FileSystemResource("c:\\user\\tomcat.keytab"));
ticketValidator.setDebug(true);
return ticketValidator;
}
#Bean
public DummyUserDetailsService dummyUserDetailsService() {
return new DummyUserDetailsService();
}
I don't know if i have to specify the keytab file because on windows no keytab or kb5.conf file is needed so the c:\user\tomcat.keytab file is empty.
Can someone help me with this please
You will need a Keytab file.
Keytab file contains keys which are required by kerberos module to decrypt the incoming kerberos token.
Keytab file is not out of the box present as it is specific to a user account in AD.It has to be generated by your system admin and give it to you.
You will need a service user (dedicated for your application). Generate keytab for it.
Copy it on your application server and specify its path in spring.
Check ktpass command on windows for more details about creating keytab.
You may also need to check for krb5 conf file, what it contains and how you can specify it inside Spring.

Include newly added data sources into route Data Source object without restarting the application server

Implemented Spring's AbstractRoutingDatasource by dynamically determining the actual DataSource based on the current context.
Refered this article : https://www.baeldung.com/spring-abstract-routing-data-source.
Here on spring boot application start up . Created a map of contexts to datasource objects to configure our AbstractRoutingDataSource. All these client context details are fetched from a database table.
#Bean
#DependsOn("dataSource")
#Primary
public DataSource routeDataSource() {
RoutingDataSource routeDataSource = new RoutingDataSource();
DataSource defaultDataSource = (DataSource) applicationContext.getBean("dataSource");
List<EstCredentials> credentials = LocalDataSourcesDetailsLoader.getAllCredentails(defaultDataSource); // fetching from database table
localDataSourceRegistrationBean.registerDataSourceBeans(estCredentials);
routeDataSource.setDefaultTargetDataSource(defaultDataSource);
Map<Object, Object> targetDataSources = new HashMap<>();
for (Credentials credential : credentials) {
targetDataSources.put(credential.getEstCode().toString(),
(DataSource) applicationContext.getBean(credential.getEstCode().toString()));
}
routeDataSource.setTargetDataSources(targetDataSources);
return routeDataSource;
}
The problem is if i add a new client details, I cannot get that in routeDataSource. Obvious reason is that these values are set on start up.
How can I achieve to add new client context and I had to re intialize the routeDataSource object.
Planning to write a service to get all the client context newly added and reset the routeDataSource object, no need to restart the server each time any changes in the client details.
A simple solution to this situation is adding #RefreshScope to the bean definition:
#Bean
#Primary
#RefreshScope
public DataSource routeDataSource() {
RoutingDataSource routeDataSource = new RoutingDataSource();
DataSource defaultDataSource = (DataSource) applicationContext.getBean("dataSource");
List<EstCredentials> credentials = LocalDataSourcesDetailsLoader.getAllCredentails(defaultDataSource); // fetching from database table
localDataSourceRegistrationBean.registerDataSourceBeans(estCredentials);
routeDataSource.setDefaultTargetDataSource(defaultDataSource);
Map<Object, Object> targetDataSources = new HashMap<>();
for (Credentials credential : credentials) {
targetDataSources.put(credential.getEstCode().toString(),
(DataSource) applicationContext.getBean(credential.getEstCode().toString()));
}
routeDataSource.setTargetDataSources(targetDataSources);
return routeDataSource;
}
Add Spring Boot Actuator as a dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
Then trigger the refresh endpoint POST to /actuator/refresh to update the DataSource (actually every refresh scoped bean).
So this will depend on how much you know about the datasources to be added, but you could set this up as a multi-tenant project. Another example of creating new datasources:
#Autowired private Map <String, Datasource> mars2DataSources;
public void addDataSourceAtRuntime() {
DataSourceBuilder dataSourcebuilder = DataSourcebuilder.create(
MultiTenantJPAConfiguration.class.getclassloader())
.driverclassName("org.postgresql.Driver")
.username("postgres")
.password("postgres")
.url("Jdbc: postgresql://localhost:5412/somedb");
mars2DataSources("tenantX", datasourcebuilder.build())
}
Given that you are using Oracle, you could also use its database change notification features.
Think of it as a listener in the JDBC driver that gets notified whenever something changes in your database table. So upon receiving a change, you could reinitialize/add datasources.
You can find a tutorial of how to do this here: https://docs.oracle.com/cd/E11882_01/java.112/e16548/dbchgnf.htm#JJDBC28820
Though, depending on your organization database notifications need some extra firewall settings for the communication to work.
Advantage: You do not need to manually call the REST Endpoint if something changes, (though Marcos Barberios answer is perfectly valid!)

How to specify Job executor to only use defined process engines?

I am setting up a Spring boot application with Camunda. I want to use a multi-tenant setup as defined in "https://docs.camunda.org/manual/7.5/user-guide/process-engine/multi-tenancy/#one-process-engine-per-tenant"
I have managed to setup multiple process engines via Java (so not with processes.xml, but coded), but there always seems to be a default Process Engine. How do i achieve a setup with only the process engines i defined?
Extra information:
each process engine uses its own datasource, derived via context
I want to avoid the default process engine, because it needs its own datasource. I dont want to setup a datasource/database for a process engine without a tenant. (if i don't setup a datasource for the default, there will be errors thrown by the job executor not getting a connection)
The setup I've tried is in the following block, but for some reason there always is a "default" processengine.
#Autowired
private ConfigurableListableBeanFactory beanFactory;
#Bean
#Order(4)
public void multipleCamunda(){
log.info("Starting Camunda Multitenant");
this.targetDatasources.entrySet().stream().forEach(entry -> {
String tenant = (String) entry.getKey();
DataSource tenantDatasource = (DataSource) entry.getValue();
SpringProcessEngineConfiguration standaloneProcessEngineConfiguration = new SpringProcessEngineConfiguration();
standaloneProcessEngineConfiguration.setDataSource(tenantDatasource);
standaloneProcessEngineConfiguration.setDatabaseSchemaUpdate("true");
standaloneProcessEngineConfiguration.setProcessEngineName(tenant);
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(fondsDatasource);
standaloneProcessEngineConfiguration.setTransactionManager(dataSourceTransactionManager);
standaloneProcessEngineConfiguration.setHistory(HistoryLevel.HISTORY_LEVEL_FULL.getName());
standaloneProcessEngineConfiguration.setJobExecutorDeploymentAware(true);
// deploy all processes from folder 'processes'
Resource[] resources = new Resource[0];
try {
resources = resourceLoader.getResources("classpath:/bpm/*.bpmn");
} catch (IOException e) {
e.printStackTrace();
}
standaloneProcessEngineConfiguration.setDeploymentResources(resources);
ProcessEngine processEngine = standaloneProcessEngineConfiguration.buildProcessEngine();
RuntimeContainerDelegate.INSTANCE.get().registerProcessEngine(processEngine);
beanFactory.registerSingleton("processEngine" + tenant,processEngine);
log.info("Started process Engine for " + tenant);
});
}
The maven dependencies i use:
<dependency>
<groupId>org.camunda.bpm</groupId>
<artifactId>camunda-engine-cdi</artifactId>
<version>7.10.0</version>
</dependency>
<dependency>
<groupId>org.camunda.bpm.javaee</groupId>
<artifactId>camunda-ejb-client</artifactId>
<version>7.10.0</version>
</dependency>
<dependency>
<groupId>org.camunda.bpm.springboot</groupId>
<artifactId>camunda-bpm-spring-boot-starter-
webapp</artifactId>
<version>3.2.1</version>
</dependency>
I assume that I need to define a #Bean for some kind of ConfigurationBean, but I can't figure out which one and how. Please tell me what configuration bean I need to autowire and how.
** Solution **
In order to stop the default initialization, you need to edit the application.yaml and add
camunda:
bpm:
enabled: false
Setting the same property in a CamdundaBpmProperties somehow doesn't seem to work.
When you have done this, the default startup won't occur and the process engine will start when adding a process engine via the code snippet posted above

Programmatic login with liberty profile without password

I try to migrate our application from WAS 8.0 to Liberty Profile at the moment.
In our application I need the possibility to do a programmatic login without having the password of the user.
In WAS 8.0 this was done with the following code snippet:
import com.ibm.websphere.security.auth.WSSubject;
import com.ibm.ws.security.core.ContextManagerFactory;
import com.ibm.websphere.security.auth.callback.WSCallbackHandlerImpl;
public class SecurityConfigJaasWasImpl implements ISecurityConfig {
public Object doAsWithoutPwd(String user, String[] roles, final ISecuredCode code) throws Exception {
final String mName ="doAs(String, String[], ISecuredCode)";
Object ret = null;
try {
if (code != null) {
ret = WSSubject.doAs(ContextManagerFactory.getInstance().login("REALM", user), new PrivilegedExceptionAction() {
/* (non-Javadoc)
* #see java.security.PrivilegedExceptionAction#run()
*/
public Object run() throws Exception {
return code.run();
}
});
}
} catch (LoginException e) {
throw new SecurityConfigException("Error login user " + user);
}
}
Unfortunately the class ContextManagerFactory is not known in Liberty.
All examples for programmatic login with liberty profile are using WSCallbackHandlerImpl to do a Jaas login. But for that I need to know the password of the user.
Is there any possibility to do something similar to my WAS 8.0 code in liberty profile?
I had this same problem when porting our application from WAS-ND 7 to Liberty. Unfortunately, there is no way to perform a programmatic login on Liberty without having access to the user's password. I have an open PMR with IBM on this (25293,082,000), and I was told that the feature is "under consideration". I also have an RFE open on this:
https://www.ibm.com/developerworks/rfe/execute?use_case=viewRfe&CR_ID=100438

Resources