Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LDAP cahcing #288

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions publish-service/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,38 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<version>5.7.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-ldap</artifactId>
<version>5.7.4</version>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.8</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.7.4</version>
</dependency>
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-core</artifactId>
<version>2.4.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
<version>${springboot.version}</version>
</dependency>

<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.ericsson.eiffel.remrem.publish.config;

import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserCache;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.cache.NullUserCache;
import org.springframework.security.core.userdetails.cache.SpringCacheBasedUserCache;
import org.springframework.security.ldap.authentication.LdapAuthenticationProvider;
import org.springframework.security.ldap.authentication.LdapAuthenticator;
import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator;
import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

public class CachingLdapAuthenticationProvider extends LdapAuthenticationProvider {

private UserCache userCache = new NullUserCache();

/**
* Create an instance with the supplied authenticator and authorities populator
* implementations.
*
* @param authenticator the authentication strategy (bind, password comparison, etc)
* to be used by this provider for authenticating users.
* @param authoritiesPopulator the strategy for obtaining the authorities for a given
*/

public CachingLdapAuthenticationProvider(LdapAuthenticator authenticator, LdapAuthoritiesPopulator authoritiesPopulator) {
super(authenticator, authoritiesPopulator);
}

public void setUserCache(UserCache userCache) {
this.userCache = userCache;
}


@Override
public Authentication authenticate(Authentication authentication) {
String userName = authentication.getName();
UsernamePasswordAuthenticationToken userToken = (UsernamePasswordAuthenticationToken) authentication;
UserDetails userDetailsFromCache = userCache.getUserFromCache(userName);
if (userDetailsFromCache != null) {
additionalAuthenticationChecks(userDetailsFromCache, userToken);
return createSuccessfulAuthentication(userToken, userDetailsFromCache);
} else {
Authentication authenticationFromProvider = super.authenticate(authentication);
userCache.putUserInCache((UserDetails)authenticationFromProvider.getPrincipal());
return authenticationFromProvider;
}

}

protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication) {
Object credentials = authentication.getCredentials();
if (!StringUtils.isEmpty(credentials)) {
String presentedPassword = authentication.getCredentials().toString();
if (userDetails.getPassword().equals(presentedPassword)) {
// password matches
return;
}
}
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,37 @@
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserCache;
import org.springframework.security.core.userdetails.cache.SpringCacheBasedUserCache;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.ldap.authentication.BindAuthenticator;
import org.springframework.security.ldap.authentication.LdapAuthenticator;
import org.springframework.security.ldap.search.FilterBasedLdapUserSearch;
import org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator;
import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.TimeUnit;

/**
* This class is used to enable the ldap authentication based on property
Expand Down Expand Up @@ -64,6 +95,9 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Value("${activedirectory.connectionTimeOut:#{127000}}")
private Integer ldapTimeOut = DEFAULT_LDAP_CONNECTION_TIMEOUT;

@Value("${LdapCacheTTL}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a default TTL? I mean something like for ldapTimeOut, see line 95.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The property should be part of configuration file (including appropriate description).

private Integer LdapCacheTTL;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Java conventions should be followed: start with lower-case letter.


// built in connection timeout value for ldap if the network issue happens
public static final Integer DEFAULT_LDAP_CONNECTION_TIMEOUT = 127000;

Expand All @@ -74,20 +108,54 @@ public Integer getTimeOut() {
@Autowired
private CustomAuthenticationEntryPoint customAuthenticationEntryPoint;

@Autowired
protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
@Bean
public UserCache userCache() {
if (cacheManager().getCache("authenticationCache") == null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, define a constant for "authenticationCache".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I don't understand it, but
cacheManager().getCache("authenticationCache") == null
should alway be true. But maybe I don't understand something...

I was unable to run it in debugger. We should have a call and discuss that.

throw new IllegalStateException ("Cache 'authenticationCache' is required but not available");
}
return new SpringCacheBasedUserCache(cacheManager().getCache("authenticationCache"));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should always a new instance be created? Is it just a wrapper?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refactored to use a single isntance

}

@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
.expireAfterWrite(LdapCacheTTL, TimeUnit.MINUTES));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be in minutes? All other timeouts are in seconds. It'll be very confused for users...

return cacheManager;
}

@Bean
public LdapAuthoritiesPopulator ldapAuthoritiesPopulator() {
LdapContextSource contextSource = ldapContextSource();
return new DefaultLdapAuthoritiesPopulator(contextSource, null);
}

@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
final String jasyptKey = RabbitMqPropertiesConfig.readJasyptKeyFile(jasyptKeyFilePath);
if (managerPassword.startsWith("{ENC(") && managerPassword.endsWith("}")) {
managerPassword = DecryptionUtils.decryptString(
managerPassword.substring(1, managerPassword.length() - 1), jasyptKey);
}
LOGGER.debug("LDAP server url: " + ldapUrl);
auth.ldapAuthentication()
.userSearchFilter(userSearchFilter)
.contextSource(ldapContextSource());
LdapContextSource contextSource = ldapContextSource();
BindAuthenticator bindAuthenticator = new BindAuthenticator(contextSource);
bindAuthenticator.setUserSearch(new FilterBasedLdapUserSearch("", userSearchFilter, contextSource));


LdapAuthoritiesPopulator ldapAuthoritiesPopulator = ldapAuthoritiesPopulator();

// Create and use the caching LDAP authentication provider
CachingLdapAuthenticationProvider cachingProvider =
new CachingLdapAuthenticationProvider(bindAuthenticator, ldapAuthoritiesPopulator);

cachingProvider.setUserCache(userCache());
auth.authenticationProvider(cachingProvider);

}

public BaseLdapPathContextSource ldapContextSource() {
@Bean
public LdapContextSource ldapContextSource() {
LdapContextSource ldap = new LdapContextSource();
ldap.setUrl(ldapUrl);
ldap.setBase(rootDn);
Expand Down