How to Over ride BindAuthenticator handleBindException for Spring LDAP Authentication setup in Spring Boot - spring

For Spring security setup in Spring Boot. The LDAP Authentication provider is configured by default to use BindAuthenticator class.
This Class contains method
/**
* Allows subclasses to inspect the exception thrown by an attempt to bind with a
* particular DN. The default implementation just reports the failure to the debug
* logger.
*/
protected void handleBindException(String userDn, String username, Throwable cause) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to bind as " + userDn + ": " + cause);
}
}
This Method is to handle the authentication related Exceptions like invalid credentials.
I want to over-ride this method so i can handle this issue and return proper error message on the basis of error codes returned by LDAP. like invalid password or the account is locked.
Current LDAP implementation always returns "Bad Credentials" that does not give the right picture that why my credentials are invalid. i want to cover the cases
where the account is Locked
password is expired so i can redirect to change password
account locked due to number of invalid password retries
Please help

The issue i fixed by defining the LDAP context instead of using the Spring Boot LDAPAuthenticationProviderConfigurer.
Then created the FilterBasedLdapUserSearch and Over-written the BindAuthentication with my ConnectBindAuthenticator.
i created a separate LDAPConfiguration class for spring boot configuration and registered all these custom objects as Beans.
From the above Objects i created LDAPAuthenticationProvider by passing my Custom Objects to constructor
The Config is as below
#Bean
public DefaultSpringSecurityContextSource contextSource() {
DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(env.getProperty("ldap.url"));
contextSource.setBase(env.getProperty("ldap.base"));
contextSource.setUserDn(env.getProperty("ldap.managerDn"));
contextSource.setPassword(env.getProperty("ldap.managerPassword"));
return contextSource;
}
#Bean
public ConnectBindAuthenticator bindAuthenticator() {
ConnectBindAuthenticator connectBindAuthenticator = new ConnectBindAuthenticator(contextSource());
connectBindAuthenticator.setUserSearch(ldapUserSearch());
connectBindAuthenticator.setUserDnPatterns(new String[]{env.getProperty("ldap.managerDn")});
return connectBindAuthenticator;
}
#Bean
public LdapUserSearch ldapUserSearch() {
return new FilterBasedLdapUserSearch("", env.getProperty("ldap.userSearchFilter"), contextSource());
}

You have to change your spring security configuration to add your extension of BindAuthenticator:
CustomBindAuthenticator.java
public class CustomBindAuthenticator extends BindAuthenticator {
public CustomBindAuthenticator(BaseLdapPathContextSource contextSource) {
super(contextSource);
}
#Override
protected void handleBindException(String userDn, String username, Throwable cause) {
// TODO: Include here the logic of your custom BindAuthenticator
if (somethingHappens()) {
throw new MyCustomException("Custom error message");
}
super.handleBindException(userDn, username, cause);
}
}
spring-security.xml
<beans:bean id="contextSource"
class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
<beans:constructor-arg value="LDAP_URL" />
<beans:property name="userDn" value="USER_DN" />
<beans:property name="password" value="PASSWORD" />
</beans:bean>
<beans:bean id="userSearch"
class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
<beans:constructor-arg index="0" value="USER_SEARCH_BASE" />
<beans:constructor-arg index="1" value="USER_SEARCH_FILTER" />
<beans:constructor-arg index="2" ref="contextSource" />
</beans:bean>
<beans:bean id="ldapAuthProvider"
class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
<beans:constructor-arg>
<beans:bean class="com.your.project.CustomBindAuthenticator">
<beans:constructor-arg ref="contextSource" />
<beans:property name="userSearch" ref="userSearch" />
</beans:bean>
</beans:constructor-arg>
</beans:bean>
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="ldapAuthProvider" />
</security:authentication-manager>
Hope it's helpful.

Related

HttpSession setMaxInactiveInterval do not trigger Spring Security session SessionDestroyedEvent

I have a listener to set session timeout dynamically
public class DynamicSessionTimeoutHttpSessionListener implements HttpSessionListener{
#Override
public void sessionCreated(HttpSessionEvent event) {
long sessionTimeoutInMinute = ConfigEntryMgr.getInstance().getByKeyAsLong(ConfigEntryKeys.SESSION_TIMEOUT_IN_MINUTE);
event.getSession().setMaxInactiveInterval((int) sessionTimeoutInMinute * 60);
}
#Override
public void sessionDestroyed(HttpSessionEvent event) {
}
}
In spring security I do not allow concurrent session:
<beans:bean id="sas"
class="org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy">
<beans:constructor-arg>
<beans:list>
<beans:bean class="org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy">
<beans:constructor-arg ref="sessionRegistry" />
<beans:property name="maximumSessions" value="1" />
<beans:property name="exceptionIfMaximumExceeded" value="true" />
</beans:bean>
<beans:bean class="org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy">
</beans:bean>
<beans:bean
class="org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy">
<beans:constructor-arg ref="sessionRegistry" />
</beans:bean>
</beans:list>
</beans:constructor-arg>
</beans:bean>
As a result nobody can login with a username that already is logged in.
The problem is that setMaxInactiveInterval does not destroy spring session.
Unless first user open the browser again and do an action, the servlet does not find out this session has expired to trigger SessionDestroyedEvent.
Now if the first user just close his browser, no body can login with this user forever, because Spring Security thinks somebody is still logged in with this username

How to replicate user from LDAP to application database for handling authorization from application layer

Hi I am not pretty sure about the LDAP and spring security. I have a requirement were as the application authentication has to be carried out by a LDAP and authorization mechanism has to handled by application layer. I am using Jhipster which has spring security implementation. However, I can able to connect to LDAP and authenticate the user.
Now authorization mechanism has to be handled by application layer where I could manage authorities. So I thought of replicating the user information from the LDAP to application layer database if the user is not present just after the user authentication process. So how can I implement this with spring security framework. How to intercept the filter chain or some process to do this.
And finally is this the good approach or is there a better way to handle this.
This is how I implemented LDAP authentication and local Authorization in my project.
Configuration:
<beans:bean id="ldapAuthProvider"
class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
<beans:constructor-arg name="authenticator">
<beans:bean
class="org.springframework.security.ldap.authentication.BindAuthenticator">
<beans:constructor-arg ref="contextSource" />
<beans:property name="userSearch">
<beans:bean
class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
<beans:constructor-arg name="searchBase"
value="ou=example,dc=springframework,dc=org" />
<beans:constructor-arg name="searchFilter"
value="(uid={0})" />
<beans:constructor-arg name="contextSource"
ref="contextSource" />
</beans:bean>
</beans:property>
</beans:bean>
</beans:constructor-arg>
<beans:constructor-arg name="authoritiesPopulator"
ref="myLDAPAuthPopulator" />
</beans:bean>
<authentication-manager alias="authenticationManager">
<authentication-provider ref="ldapAuthProvider" />
</authentication-manager>
Custom Authorities Populator:
#Component("myLDAPAuthPopulator")
public class MyLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator {
#Autowired
private UserDao userDao;
#Override
public Collection<? extends GrantedAuthority> getGrantedAuthorities(
DirContextOperations userData, String username) {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
User user = userDao.searchUser(username);
List<String> roleList = userDao.getRoles(username);
if (!roleList.isEmpty()) {
for (String role : roleList) {
System.out.println(role);
authorities.add(new SimpleGrantedAuthority(role));
}
}
return authorities;
}

check if user subscription for trial period is expire or not using spring MVC

I am using spring MVC and want to check if user's trial period has expired.
I am getting user detail using spring security using the following method
public User getUserDetail() {
Authentication auth = SecurityContextHolder.getContext()
.getAuthentication();
Object principal = auth.getPrincipal();
if(principal instanceof User){
User user = (User) principal;
return user;
}
return null;
}
User object contains the date when he logged in first.
I am checking the user subscription using following code
UserBean userLoggedIn = (UserBean) userService.getUserDetail();
Date dt = userLoggedIn.getUserCreationDate();
DateTime userCreated = new DateTime(dt).plusDays(TRIAL_PERIOD);
DateTime currentDateTime = new DateTime();
if(currentDateTime.compareTo(userCreated) > 0 && userLoggedIn.getPackageType() == 0){
return new ModelAndView("pricing","user",userLoggedIn);
}
Now my problem is I don't want to write the above code repeatedly in each controller. So is there any common place where I can check the user trial period expire or not and redirect him to pricing page.
I have CustomUserDetail class where I am accessing user details from database and put it in spring security session. So I think this should be the best place to check if users trial period is expire or not but I don't know how I can redirect user from this class to pricing page.
My CustomUserDetail class is
#Service
#Transactional(readOnly = true)
public class CustomUserDetailsService implements UserDetailsService {
static final Logger logger = Logger.getLogger(CustomUserDetailsService.class);
#Resource(name="userService")
private UserService userService;
/* (non-Javadoc)
* #see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
*/
#Override
public UserDetails loadUserByUsername(String email)
throws UsernameNotFoundException, DataAccessException {
try {
boolean enabled = true;
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
UserBean domainUser = userService.getUserByName(email);
domainUser.isEnabled();
domainUser.isAccountNonExpired();
domainUser.isCredentialsNonExpired();
domainUser.isAccountNonLocked();
//Collection<? extends GrantedAuthority> roles = getAuthorities((long) domainUser.getRoleId());
return domainUser;
} catch (Exception e) {
logger.error("Invalid Login.",e);
throw new RuntimeException(e);
}
}
---updated---
My spring-security.xml is
<form-login login-page="/login.htm"
authentication-failure-url="/loginfailed.htm"
authentication-failure-handler-ref="exceptionMapper"
default-target-url="/index.htm"
always-use-default-target="true"/>
<access-denied-handler error-page="/logout.htm"/>
<logout invalidate-session="true"
logout-url="/logout.htm"
success-handler-ref="userController"/>
<remember-me user-service-ref="customUserDetailsService" key="89dqj219dn910lsAc12" use-secure-cookie="true" token-validity-seconds="466560000"/>
<session-management session-authentication-strategy-ref="sas"/>
</http>
<authentication-manager>
<authentication-provider user-service-ref="customUserDetailsService">
<password-encoder ref="customEnocdePassword" >
<salt-source user-property="email"/>
</password-encoder>
</authentication-provider>
</authentication-manager>
<beans:bean id="customEnocdePassword" class="com.mycom.myproj.utility.CustomEnocdePassword" />
<beans:bean id="exceptionMapper" class="org.springframework.security.web.authentication.ExceptionMappingAuthenticationFailureHandler" >
<beans:property name="exceptionMappings">
<beans:map>
<beans:entry key="your.package.TrialPeriodExpiredException" value="/pricing"/>
</beans:map>
</beans:property>
</beans:bean>
<beans:bean id="sas"
class="org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy">
<beans:constructor-arg name="sessionRegistry" ref="sessionRegistry" />
<beans:property name="maximumSessions" value="3" />
---update----
Now what I did is
<beans:bean id="authenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<beans:property name="userDetailsService" ref="customUserDetailsService"/>
<beans:property name="passwordEncoder" ref="customEnocdePassword"/>
<beans:property name="preAuthenticationChecks" ref="expirationChecker"/>
</beans:bean>
<authentication-manager>
<authentication-provider user-service-ref="authenticationProvider">
<password-encoder ref="customEnocdePassword" >
<salt-source user-property="email"/>
</password-encoder>
</authentication-provider>
</authentication-manager>
<!-- <authentication-manager>
<authentication-provider user-service-ref="customUserDetailsService">
<password-encoder ref="customEnocdePassword" >
<salt-source user-property="email"/>
</password-encoder>
</authentication-provider>
</authentication-manager> -->
<beans:bean id="expirationChecker" class="com.mycom.myproj.utility.UserTrialPeriodExpirationChecker" />
<beans:bean id="customEnocdePassword" class="com.mycom.myproj.utility.CustomEnocdePassword" />
now I am getting below error
"Cannot convert value of type [org.springframework.security.authentication.dao.DaoAuthenticationProvider]
to required type [org.springframework.security.core.userdetails.UserDetailsService]
for property 'userDetailsService': no matching editors or conversion strategy found"
You could set a custom UserDetailsChecker on the DaoAuthenticationProvider that verifies the expiration date before authenticating the user.
The <authentication-provider> element in your config generates a DaoAuthenticationProvider, but there is no attribute on that element that would allow you to set its preAuthenticationChecks property. In order to work around this limitation of the namespace configuration, you will have to fall back to defining that provider as a normal bean:
<bean id="authenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="customUserDetailsService"/>
<property name="passwordEncoder" ref="customEnocdePassword"/>
<property name="preAuthenticationChecks" ref="expirationChecker"/>
</bean>
and refer to it by the id in the <authentication-manager> config:
<security:authentication-manager>
<security:authentication-provider ref="authenticationProvider"/>
</security:authentication-manager>
The above referenced expirationChecker bean must implement UserDetailsChecker which is a call-back interface receiving the UserDetails object, where you could throw a specific exception if the user's trial period has expired:
public class UserTrialPeriodExpirationChecker implements UserDetailsChecker {
#Override
public void check(UserDetails user) {
if( /* whatever way you check expiration */ ) {
throw new TrialPeriodExpiredException();
}
if (!user.isAccountNonLocked()) {
throw new LockedException("User account is locked");
}
if (!user.isEnabled()) {
throw new DisabledException("User is disabled");
}
if (!user.isAccountNonExpired()) {
throw new AccountExpiredException("User account has expired");
}
}
}
Note that the last three checks are not related to the expiration checking, but you have to have them here, as the default implementation (which is AbstractUserDetailsAuthenticationProvider.DefaultPreAuthenticationChecks) is now overridden by this class. Since the default implementation is a private inner class, you cannot simply extend it, but need to copy the code from there to prevent locked/disabled/etc. users from logging in.
Once you have all that in place, configure an ExceptionMappingAuthenticationFailureHandler that maps your TrialPeriodExpiredException to the URL of the pricing page, where the user should land.
<form-login authentication-failure-handler-ref="exceptionMapper" ... />
...
<bean id="exceptionMapper" class="org.springframework.security.web.authentication.ExceptionMappingAuthenticationFailureHandler" >
<property name="exceptionMappings">
<map>
<entry key="your.package.TrialPeriodExpiredException" value="/pricing"/>
</map>
</property>
</bean>

Spring security - Get all logged in principals

First of all! Thank you for reading my question.
I have a problem with retreiving all my principal objects. I use Spring version 3.2.1.RELEASE and spring security 3.1.3.RELEASE.
I did my research on the net and I found how to retrieve the principals, but after inserting my own authentication code it doesnt work anymore. Methode to retrieve all principals objects:
#RequestMapping("/loggedinusers")
public String viewAllLoggedInUsers(Model model) {
List<Object> principals = sessionRegistry.getAllPrincipals();
model.addAttribute("size", principals.size());
List<Integer> listOfUserIds = new ArrayList<Integer>();
for (Object principal : principals) {
if (principal instanceof Principal) {
listOfUserIds.add(((Principal) principal).getId());
}
}
return "/logged_in_users";
}
The above code was working before I changed some security configuration. Here is all my configuration:
<!-- bean namespave -->
<security:global-method-security jsr250-annotations="enabled" pre-post-annotations="enabled" secured-annotations="enabled" />
<security:http use-expressions="true" entry-point-ref="loginEntryPoint">
<security:intercept-url pattern="/login" access="permitAll()" />
<!-- ******* Filters ******* -->
<security:custom-filter ref="ipFormLoginFilter" position="FORM_LOGIN_FILTER"/>
<security:logout
delete-cookies="JSESSIONID"
logout-url="/logout"
logout-success-url="/login"
/>
<security:session-management session-fixation-protection="newSession">
<security:concurrency-control session-registry-alias="sessionRegistry" max-sessions="5" error-if-maximum-exceeded="false" />
</security:session-management>
</security:http>
<bean id="loginEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<constructor-arg value="/login"/>
</bean>
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="customUserAuthenticationProvider" />
</security:authentication-manager>
<bean id="ipFormLoginFilter" class="nl.irp.vadp.security.CustomIpUsernamePasswordAuthenticationFilter">
<property name="filterProcessesUrl" value="/authlogin"/>
<property name="authenticationManager" ref="authenticationManager"/>
<property name="usernameParameter" value="username"/>
<property name="passwordParameter" value="password"/>
<property name="authenticationSuccessHandler">
<bean class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
<property name="defaultTargetUrl" value="/"/>
</bean>
</property>
<property name="authenticationFailureHandler">
<bean class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<property name="defaultFailureUrl" value="/login?login_error=true"/>
</bean>
</property>
</bean>
<bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.ShaPasswordEncoder">
<constructor-arg value="512" />
</bean>
</beans>
Code::
Filter class
public final class CustomIpUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (request.getMethod().equals("POST")) {
String username = obtainUsername(request);
String password = obtainPassword(request);
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
}
Code:: Custom Authentication class
#Component
public class CustomUserAuthenticationProvider implements AuthenticationProvider {
#Autowired
UserService userService;
#Autowired
ShaPasswordEncoder shaPasswordEncoder;
public CustomUserAuthenticationProvider() {
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
final String BAD_CREDENTIALS = "test";
final String BAD_IP_ADDRESS = "test";
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
String email = token.getName();
User user = null;
if (email != null) {
user = userService.findUserByEmail(email);
}
if (user == null) {
throw new UsernameNotFoundException(BAD_CREDENTIALS + "no user found");
}
String password = user.getPassword();
String salt = user.getName();
if (!shaPasswordEncoder.isPasswordValid(password, (String) token.getCredentials(), salt)) {
throw new BadCredentialsException(BAD_CREDENTIALS + "bad password");
}
if (!user.hasIpaddress(request.getRemoteAddr())) {
throw new BadCredentialsException(BAD_IP_ADDRESS + "bad ip adress");
}
authorities.add(new SimpleGrantedAuthority("ROLE_" + user.getRole().getName().toUpperCase()));
Principal principal = new Principal(user.getEmail(), user.getPassword(), authorities, user.getId());
return new UsernamePasswordAuthenticationToken(principal, user.getPassword());
}
#Override
public boolean supports(Class<?> authentication) {
return CustomIpUsernamePasswordAuthenticationToken.class.equals(authentication);
}
}
The following listeners are added:
<!-- Listeners -->
<listener><!-- Starts up the webapp project -->
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener><!-- spring security listener -->
<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>
<!-- extra toegevoegd voor die ip ... -->
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
As the above code describes, I made my own AuthenticationProvider with an authenticate methode which authenticates the inserted data. This works perfectly (component scan is also done). Authorities in jsp ( for example) seems to work also. I seem not to understand why I cant get the registered principals.
edit:
I removed the "auto-config=true" en the tag before inserting additional information.
Hope someone can help me out.
EDIT 2:
I found out where the problem was. In my own custom filter, there is a property called:sessionAuthenticationStrategy. This field needs to be set.
I inserted the following in my filter and it works:
<property name="sessionAuthenticationStrategy" ref="sessionFixationProtectionStrategy" />
<bean id="sessionFixationProtectionStrategy" class="org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy">
Gtrz,

Spring authentication not available in a user created thread

I am using spring based authentication by implementing UserDetailsService. Following is my spring config
<authentication-manager>
<authentication-provider user-service-ref="authenticationService">
<password-encoder ref="passwordEncoder">
<salt-source ref="authenticationService"/>
</password-encoder>
</authentication-provider>
</authentication-manager>
My authentication service looks like:
public class AuthenticationServiceImpl implements AuthenticationService, UserDetailsService, SaltSource {
....
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
....
return new org.springframework.security.core.userdetails.User(username, user.getPassword(), true, true, true, true, authorities);
}
}
Now the problem is when I create a new thread from one of my spring controllers. How do I authenticate my user in that thread ?
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(loadUserByUsername), password));
I found a better solution, to set it in configuration itself. Using following code:
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetClass"
value="org.springframework.security.core.context.SecurityContextHolder"/>
<property name="targetMethod" value="setStrategyName"/>
<property name="arguments"><b:list><b:value>MODE_INHERITABLETHREADLOCAL</value></list></property>
</bean>
Works like a charm.

Resources