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.
Related
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.
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;
}
We are using spring 2.5. We have common web services to authenticate user, which takes a user name and password as input and returns true or false after validating the password. How and where should we implement this web service call? Please reply. Thanks
Right now we have following spring configuration. we want to incorporate webservice call into it.
<intercept-url pattern="/service/**" access="ROLE_ANONYMOUS, ROLE_LEARNER,ROLE_TRAININGADMINISTRATOR,ROLE_LMSADMINISTRATOR,ROLE_REGULATORYANALYST,ROLE_INSTRUCTOR"/>
<logout invalidate-session="true" logout-success-url="/login.do"/>
<anonymous /> <http-basic /> <remember-me />
</http>
<b:bean id="authenticationProcessingFilterEntryPoint" class="org.springframework.security.ui.webapp.AuthenticationProcessingFilterEntryPoint">
<b:property name="loginFormUrl" value="/login.do"/>
<b:property name="forceHttps" value="false" />
</b:bean>
<authentication-manager alias='authenticationManagerAlias'/>
<b:bean id="myAuthenticationProcessingFilter" class="org.springframework.security.ui.webapp.AuthenticationProcessingFilter">
<b:property name="defaultTargetUrl" value="/interceptor.do"/>
<b:property name="authenticationFailureUrl" value="/login.do"/>
<b:property name="authenticationManager" ref="authenticationManagerAlias"/>
<b:property name="authenticationDetailsSource" ref="vu360UserAuthenticationDetailsSource"/>
<b:property name="alwaysUseDefaultTargetUrl" value="true"/>
<custom-filter position="AUTHENTICATION_PROCESSING_FILTER"/>
</b:bean>
<b:bean class="org.springframework.security.providers.dao.DaoAuthenticationProvider">
<b:property name="userDetailsService" ref="userDetailsService"/>
<b:property name="passwordEncoder" ref="passwordEncoder"/>
<b:property name="saltSource" ref="saltSource"/>
<custom-authentication-provider/>
</b:bean>
<b:bean class="org.springframework.security.providers.dao.DaoAuthenticationProvider">
<b:property name="userDetailsService" ref="userDetailsService"/>
<custom-authentication-provider/>
</b:bean>
Implements one CustomAuthenticationProvider like:
import com.google.common.collect.Lists;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
public class CustomAuthenticationProvider implements AuthenticationProvider {
public final static Logger log = LogManager.getLogger(CustomAuthenticationProvider.class.getName());
#Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
List<GrantedAuthority> AUTHORITIES = Lists.newArrayList();
AUTHORITIES.add(new GrantedAuthority() {
#Override
public String getAuthority() {
return "ROLE_ADMIN";
}
});
return new UsernamePasswordAuthenticationToken(authentication.getName(), authentication.getCredentials(), AUTHORITIES);
}
#Override
public boolean supports(Class<? extends Object> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
and
<authentication-manager>
<authentication-provider ref="customAuthenticationProvider" >
</authentication-provider>
</authentication-manager>
<beans:bean id="customAuthenticationProvider" class="com.xkey.principal.CustomAuthenticationProvider"/>
If you want to control the authentication yourself you can create your own AuthenticationManager that calls the web service and inject it into the AuthenticationProcessingFilter. Here's an example custom AuthenticationManager, obviously you'll need to replace the example service call with whatever code you use to call your actual service.
public class CustomWebServiceAuthenticationManager implements AuthenticationManager {
public Authentication authenticate(Authentication credentials) throws AuthenticationException {
String username = credentials.getName();
String password = (String)credentials.getCredentials();
// change this to your actual web service call
boolean successfulAuthentication = myWebService.authenticate(username, password);
if(successfulAuthentication) {
// do whatever you need to do to get the correct roles for the user, this is just an example of giving every user the role "ROLE_LEARNER"
List<GrantedAuthority> roles = Collections.singletonList(new SimpleGrantedAuthority("ROLE_LEARNER"));
return new UsernamePasswordAuthenticationToken(username, password, roles);
} else {
throw new AuthenticationException("Authentication failed, invalid username or password");
}
}
}
Then add the CustomWebServiceAuthenticationManager to your spring configuration and reference it in the AuthenticationProcessingFilter.
<b:bean id="customWebServiceAuthenticationManager" class="CustomWebServiceAuthenticationManager"/>
<b:bean id="myAuthenticationProcessingFilter" class="org.springframework.security.ui.webapp.AuthenticationProcessingFilter">
<b:property name="defaultTargetUrl" value="/interceptor.do"/>
<b:property name="authenticationFailureUrl" value="/login.do"/>
<b:property name="authenticationManager" ref="customWebServiceAuthenticationManager"/>
<b:property name="authenticationDetailsSource" ref="vu360UserAuthenticationDetailsSource"/>
<b:property name="alwaysUseDefaultTargetUrl" value="true"/>
<custom-filter position="AUTHENTICATION_PROCESSING_FILTER"/>
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>
I can't seem to get a cookie of spring that remembers the session, nor is the persistent_logins table of the dataSource getting populated. Why isn't the cookie received by the client?
Application context xml file :
<?xml version="1.0" encoding="UTF-8"?>
<bean:beans>
<http>
...
<remember-me data-source-ref="dataSource"
user-service-ref="userService" />
</http>
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="userService">
<password-encoder hash="md5" ref="passwordEncoder">
<salt-source ref="saltSource" />
</password-encoder>
</authentication-provider>
</authentication-manager>
...
</bean:beans>
login method:
#Service
public class AServiceImpl extends RemoteServiceServlet implements AService {
#Override
public boolean login(String username, String password, boolean remember) {
Collection<GrantedAuthority> auths = userDetailsService.getGrantedAuthorities(user);
auth = new UsernamePasswordAuthenticationToken(username, password, auths);
Authentication result = authenticationManager.authenticate(auth);
SecurityContextHolder.getContext().setAuthentication(result);
getThreadLocalRequest().getSession().setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,SecurityContextHolder.getContext());
rememberMeServices.loginSuccess(getThreadLocalRequest(),getThreadLocalResponse(), auth);
}
}