Spring security switch to Ldap authentication and database authorities - java

I implemented database authentication for my web page and web service.
It work well for both, now I have to add Ldap authentication.
I have to authenticate through remote Ldap server (using username and password) and if the user exists I have to use my database for user roles (in my database username is the same username of Ldap).
So I have to switch from my actual code to the Ldap and database authentication as above explained. My code is:
SecurityConfig class
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, proxyTargetClass = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("userDetailsService")
UserDetailsService userDetailsService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder(){
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.antMatcher("/client/**")
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
}
#Configuration
#Order(2)
public static class FormWebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
public void configure(WebSecurity web) throws Exception {
web
//Spring Security ignores request to static resources such as CSS or JS files.
.ignoring()
.antMatchers("/static/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests() //Authorize Request Configuration
//the / and /register path are accepted without login
//.antMatchers("/", "/register").permitAll()
//the /acquisition/** need admin role
//.antMatchers("/acquisition/**").hasRole("ADMIN")
//.and().exceptionHandling().accessDeniedPage("/Access_Denied");
//all the path need authentication
.anyRequest().authenticated()
.and() //Login Form configuration for all others
.formLogin()
.loginPage("/login")
//important because otherwise it goes in a loop because login page require authentication and authentication require login page
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/login?logout")
.permitAll();
// CSRF tokens handling
}
}
MyUserDetailsService class
#Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
#Autowired
private UserServices userServices;
static final Logger LOG = LoggerFactory.getLogger(MyUserDetailsService.class);
#Transactional(readOnly=true)
#Override
public UserDetails loadUserByUsername(final String username){
try{
com.domain.User user = userServices.findById(username);
if (user==null)
LOG.error("Threw exception in MyUserDetailsService::loadUserByUsername : User doesn't exist" );
else{
List<GrantedAuthority> authorities = buildUserAuthority(user.getUserRole());
return buildUserForAuthentication(user, authorities);
}
}catch(Exception e){
LOG.error("Threw exception in MyUserDetailsService::loadUserByUsername : " + ErrorExceptionBuilder.buildErrorResponse(e)); }
return null;
}
// Converts com.users.model.User user to
// org.springframework.security.core.userdetails.User
private User buildUserForAuthentication(com.domain.User user, List<GrantedAuthority> authorities) {
return new User(user.getUsername(), user.getPassword(), user.isEnabled(), true, true, true, authorities);
}
private List<GrantedAuthority> buildUserAuthority(Set<UserRole> userRoles) {
Set<GrantedAuthority> setAuths = new HashSet<GrantedAuthority>();
// Build user's authorities
for (UserRole userRole : userRoles) {
setAuths.add(new SimpleGrantedAuthority(userRole.getUserRoleKeys().getRole()));
}
List<GrantedAuthority> Result = new ArrayList<GrantedAuthority>(setAuths);
return Result;
}
so I have to:
1)access of user from login page for web pages and username and password for web services. This has to be done through Ldap.
2)the username of user needs for database query to authenticate user.
Do you have any idea how I can implement this?
Thanks
UPDATE WITH RIGHT CODE: Following the #M. Deinum advice I create MyAuthoritiesPopulator class instead of MyUserDetailsService and authentication with database and Ldap works:
#Service("myAuthPopulator")
public class MyAuthoritiesPopulator implements LdapAuthoritiesPopulator {
#Autowired
private UserServices userServices;
static final Logger LOG = LoggerFactory.getLogger(MyAuthoritiesPopulator.class);
#Transactional(readOnly=true)
#Override
public Collection<? extends GrantedAuthority> getGrantedAuthorities(DirContextOperations userData, String username) {
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
try{
com.domain.User user = userServices.findById(username);
if (user==null)
LOG.error("Threw exception in MyAuthoritiesPopulator::getGrantedAuthorities : User doesn't exist into ATS database" );
else{
for(UserRole userRole : user.getUserRole()) {
authorities.add(new SimpleGrantedAuthority(userRole.getUserRoleKeys().getRole()));
}
return authorities;
}
}catch(Exception e){
LOG.error("Threw exception in MyAuthoritiesPopulator::getGrantedAuthorities : " + ErrorExceptionBuilder.buildErrorResponse(e)); }
return authorities;
}
}
and I changed SecurityConfig as below:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, proxyTargetClass = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("myAuthPopulator")
LdapAuthoritiesPopulator myAuthPopulator;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.contextSource()
.url("ldap://127.0.0.1:10389/dc=example,dc=com")
// .managerDn("")
// .managerPassword("")
.and()
.userSearchBase("ou=people")
.userSearchFilter("(uid={0})")
.ldapAuthoritiesPopulator(myAuthPopulator);
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.antMatcher("/client/**")
.authorizeRequests()
//Excluede send file from authentication because it doesn't work with spring authentication
//TODO add java authentication to send method
.antMatchers(HttpMethod.POST, "/client/file").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic();
}
}
#Configuration
#Order(2)
public static class FormWebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
public void configure(WebSecurity web) throws Exception {
web
//Spring Security ignores request to static resources such as CSS or JS files.
.ignoring()
.antMatchers("/static/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests() //Authorize Request Configuration
//the "/" and "/register" path are accepted without login
//.antMatchers("/", "/register").permitAll()
//the /acquisition/** need admin role
//.antMatchers("/acquisition/**").hasRole("ADMIN")
//.and().exceptionHandling().accessDeniedPage("/Access_Denied");
//all the path need authentication
.anyRequest().authenticated()
.and() //Login Form configuration for all others
.formLogin()
.loginPage("/login")
//important because otherwise it goes in a loop because login page require authentication and authentication require login page
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/login?logout")
.permitAll();
}
}
}
My LDAP development environment created in Apache directory studio

Spring Security already supports LDAP out-of-the-box. It actually has a whole chapter on this.
To use and configure LDAP add the spring-security-ldap dependency and next use the AuthenticationManagerBuilder.ldapAuthentication to configure it. The LdapAuthenticationProviderConfigurer allows you to set the needed things up.
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.contextSource()
.url(...)
.port(...)
.managerDn(...)
.managerPassword(...)
.and()
.passwordEncoder(passwordEncoder())
.userSearchBase(...)
.ldapAuthoritiesPopulator(new UserServiceLdapAuthoritiesPopulater(this.userService));
}
Something like that (it should give you at least an idea on what/how to configure things) there are more options but check the javadocs for that. If you cannot use the UserService as is to retrieve the roles (because only the roles are in the database) then implement your own LdapAuthoritiesPopulator for that.

You need to create a CustomAuthenticationProvider wich implements AuthenticationProvider, and override authenticate method, for example:
#Component
public class CustomAuthenticationProvider
implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
boolean authenticated = false;
/**
* Here implements the LDAP authentication
* and return authenticated for example
*/
if (authenticated) {
String usernameInDB = "";
/**
* Here look for username in your database!
*
*/
List<GrantedAuthority> grantedAuths = new ArrayList<>();
grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));
Authentication auth = new UsernamePasswordAuthenticationToken(usernameInDB, password, grantedAuths);
return auth;
} else {
return null;
}
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
Then, in your SecurityConfig, you need to override the configure thats use AuthenticationManagerBuilder:
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(this.authenticationProvider);
}
You can autowire the CustomAuthenticationProvider doing this:
#Autowired
private CustomAuthenticationProvider authenticationProvider;
Doing this, you can override the default authentication behaviour.

I also found this chapter Spring Docu Custom Authenicator and build my own switch between LDAP and my DB users. I can effortlessy switch between login data with set priorities (in my case LDAP wins).
I have configured an LDAP with the yaml configuration files for the LDAP user data which I don't disclose here in detail. This can be easily done with this Spring Docu LDAP Configuration.
I stripped the following example off the clatter such as logger/javadoc etc. to highlight the important parts. The #Order annotation determines the priorities in which the login data is used. The in memory details are hardcoded debug users for dev only purposes.
SecurityWebConfiguration
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Inject
private Environment env;
#Inject
private LdapConfiguration ldapConfiguration;
#Inject
private BaseLdapPathContextSource contextSource;
#Inject
private UserDetailsContextMapper userDetailsContextMapper;
#Inject
private DBAuthenticationProvider dbLogin;
#Inject
#Order(10) // the lowest number wins and is used first
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(new InMemoryUserDetailsManager(getInMemoryUserDetails()));
}
#Inject
#Order(11) // the lowest number wins and is used first
public void configureLDAP(AuthenticationManagerBuilder auth) throws Exception {
if (ldapConfiguration.isLdapEnabled()) {
auth.ldapAuthentication().userSearchBase(ldapConfiguration.getUserSearchBase())
.userSearchFilter(ldapConfiguration.getUserSearchFilter())
.groupSearchBase(ldapConfiguration.getGroupSearchBase()).contextSource(contextSource)
.userDetailsContextMapper(userDetailsContextMapper);
}
}
#Inject
#Order(12) // the lowest number wins and is used first
public void configureDB(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(dbLogin);
}
}
DB Authenticator
#Component
public class DBAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String name = authentication.getName();
String password = authentication.getCredentials().toString();
// your code to compare to your DB
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
/**
* #param original <i>mandatory</i> - input to be hashed with SHA256 and HEX encoding
* #return the hashed input
*/
private String sha256(String original) {
MessageDigest md = null;
try {
md = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new AuthException("The processing of your password failed. Contact support.");
}
if (false == Strings.isNullOrEmpty(original)) {
md.update(original.getBytes());
}
byte[] digest = md.digest();
return new String(Hex.encodeHexString(digest));
}
private class AuthException extends AuthenticationException {
public AuthException(final String msg) {
super(msg);
}
}
}
Feel free to ask details. I hope this is useful for someone else :D

For anyone using grails it is much simpler. Simply add this to your config:
grails:
plugin:
springsecurity:
ldap:
authorities:
retrieveDatabaseRoles: true

Related

Spring Security and Oauth2 misunderstanding

I am currently working on a Spring Boot application and I have the task to do the security of the application. They suggested to use OAuth2 token authentification even thought in other applications I manage to create the security with other spring security tutorial.
This are created based on tutorials I found on different sources:
public class OAuthPermissionConfig extends ResourceServerConfigurerAdapter
#Override
public void configure(HttpSecurity http) throws Exception {
http.anonymous().disable()
.authorizeRequests()
.antMatchers("/pim/oauth/token").permitAll().and().formLogin()
.and().authorizeRequests().antMatchers("/actuator/**", "/v2/api-docs", "/webjars/**",
"/swagger-resources/configuration/ui", "/swagger-resources", "/swagger-ui.html",
"/swagger-resources/configuration/security").hasAnyAuthority("ADMIN")
.anyRequest().authenticated();
}
public class CustomAuthenticationProvider implements AuthenticationProvider
#Autowired
private ADService adService;
#Autowired
private UserService userService;
#Override
#Transactional
public Authentication authenticate(Authentication authentication) {
try {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
User user = userService.getUserByUsername(username);
userService.isUserAllowedToUseTheApplication(user);
if (adService.isUserNearlyBlockedInAD(user)) {
throw new BadCredentialsException(CustomMessages.TOO_MANY_LOGIN_FAILED);
} else {
adService.login(username, password);
}
List<GrantedAuthority> userAuthority = user.getRoles().stream()
.map(p -> new SimpleGrantedAuthority(p.getId())).collect(Collectors.toList());
return new LoginToken(user, password, userAuthority);
} catch (NoSuchDatabaseEntryException | NullArgumentException | NamingException | EmptyUserRolesException e) {
throw new BadCredentialsException(CustomMessages.INVALID_CREDENTIALS + " or " + CustomMessages.UNAUTHORIZED);
}
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(
UsernamePasswordAuthenticationToken.class);
}
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public PasswordEncoder getPasswordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
public class OAuthServerConfig extends AuthorizationServerConfigurerAdapter
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private UserService userService;
#Autowired
private PasswordEncoder passwordEncoder;
#Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.authenticationManager(authenticationManager).tokenEnhancer(tokenEnhancer());
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient("pfjA#Dmin")
.secret(passwordEncoder.encode("4gM~$laY{gnfShpa%8Pcjwcz-J.NVS"))
.authorizedGrantTypes("password")
.accessTokenValiditySeconds(UTILS.convertMinutesToSeconds(1440))
.scopes("read", "write", "trust")
.resourceIds("oauth2-resource");
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) {
security.checkTokenAccess("isAuthenticated()").allowFormAuthenticationForClients();
}
When testing the login, I use postman with this parameters :
http://localhost:8080/oauth/token?grant_type=password
Headers: Basic btoa(pfjA#Dmin,4gM~$laY{gnfShpa%8Pcjwcz-J.NVS)
Content-Type : application/x-www-form-urlencoded
Body: form-data -> username and pass
that should be a valid user credentials from the database.
And the user will respond if the credentials are correct
"access_token": "f0dd6eee-7a64-4079-bb1e-e2cbcca6d7bf",
"token_type": "bearer",
"expires_in": 86399,
"scope": "read write trust"
Now I have to use this token for all the other requests otherwise I dont have any permision to use the application.
My question: Is this other version of Spring Security or what? I read about OAuth2 authentication but I read that an application can have BOTH Spring Security and OAuth2. Can someone please explain me if there is something wrong with the way we decided to implement the app security?
Thank you very much!
Yes,you can think it's a different version of spring security,it replaces some strategies of standard spring security,such as the authorization checking of requests.

Put user in HttpSession with Spring Security default login and authenticate

I precise that I am a french student in 1st year of Java Developper.
I'm developing a little multi-module app using: Spring Boot, Spring security, Hibernate, Spring Data, Spring MVC and Thymeleaf.
I would like to set the User in the session, or at least the userId, at login. This way I don't have to put it manually in the session or in the model each time I need it.
But as I use the default Spring Security login and authentication configuration, I really don't know how or where to call such a method:
void putUserInHttpSession( HttpSession httpSession ) {
httpSession.setAttribute( "user" , getManagerFactory().getUserManager().findByUserName( SecurityContextHolder.getContext().getAuthentication().getName()) );
}
I can do it eahc time I need it but I find it pretty ugly not to just do this at login in!
Here are what I think you might need to help me (that would be AWESOME !!! :)
My WebSecurityConfig class:
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsServiceImpl userDetailsService;
#Autowired
private DataSource dataSource;
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// Setting Service to find User in the database.
// And Setting PassswordEncoder
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure( HttpSecurity http ) throws Exception {
http.csrf().disable();
// /userInfo page requires login as ROLE_USER or ROLE_ADMIN.
// If no login, it will redirect to /login page.
http.authorizeRequests().antMatchers(
"/user/**")
.access("hasAnyRole('ROLE_USER', 'ROLE_ADMIN')");
// For ADMIN only.
http.authorizeRequests().antMatchers(
"/admin/**")
.access("hasRole('ROLE_ADMIN')");
// When the user has logged in as XX.
// But access a page that requires role YY,
// AccessDeniedException will be thrown.
http.authorizeRequests().and().exceptionHandling().accessDeniedPage("/public/403");
// Config for Login Form
http.authorizeRequests().and().formLogin()//
// Submit URL of login page.
.loginProcessingUrl("/j_spring_security_check") // Submit URL
.loginPage("/public/login").defaultSuccessUrl("/public/showAtlas")//
.failureUrl("/public/login?error=true")//
.usernameParameter("username")//
.passwordParameter("password")
//Config for Logout Page
.and()
.logout().logoutUrl("/public/logout").logoutSuccessUrl("/public/logoutSuccessful");
http.authorizeRequests().antMatchers(
"/public/**").permitAll();
// The pages does not require login
}
}
My UserDetailsServiceImpl class:
#Service
public class UserDetailsServiceImpl implements UserDetailsService{
#Autowired
private ManagerFactory managerFactory;
// private HttpSession httpSession;
/**
* The authentication method uses the user email, since it is easier to remember for most users
* #param input
* #return a UserDetails object
* #throws UsernameNotFoundException
*/
#Override
public UserDetails loadUserByUsername( String input) throws UsernameNotFoundException {
User user = new User();
if( input.contains( "#" )){
user = this.managerFactory.getUserManager().findByEmail( input );
}
else {
user = this.managerFactory.getUserManager().findByUserName( input );
}
if (user == null) {
throw new UsernameNotFoundException( "User with email " + input + " was not found in the database" );
}
// [ROLE_USER, ROLE_ADMIN,..]
List<String> roleNames = this.managerFactory.getRoleManager().findRoleByUserName(user.getUserName());
List<GrantedAuthority> grantList = new ArrayList<GrantedAuthority>();
if (roleNames != null) {
for (String role : roleNames) {
// ROLE_USER, ROLE_ADMIN,..
GrantedAuthority authority = new SimpleGrantedAuthority(role);
grantList.add(authority);
}
}
return (UserDetails) new org.springframework.security.core.userdetails.User(user.getUserName(),
user.getPassword(), grantList);
}
}
My simple LoginController:
#Controller
public class LoginController{
#GetMapping("/public/login")
public String login(Model model ){
return "view/login";
}
#GetMapping("/public/logoutSuccessful")
public String logout(Model model) {
return "view/logoutSuccessful";
}
So, is there a simple way to put the user or userId in the httpSession at login?
Thank you very much guys!!!
THE SOLUTION
Create a CustomAuthenticationSuccessHandler
#Component
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
#Autowired
private ManagerFactory managerFactory;
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication)
throws IOException, ServletException {
String userName = "";
HttpSession session = request.getSession();
Collection< GrantedAuthority > authorities = null;
if(authentication.getPrincipal() instanceof Principal ) {
userName = ((Principal)authentication.getPrincipal()).getName();
session.setAttribute("role", "none");
}else {
User userSpringSecu = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
session.setAttribute("role", String.valueOf( userSpringSecu.getAuthorities()));
session.setAttribute( "connectedUser" , managerFactory.getUserManager().findByUserName( userSpringSecu.getUsername() ) );
}
response.sendRedirect("/public/showAtlas" );
}
}
Then Autowired this class and add it in the WebSecurityConfigurerAdapter
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsServiceImpl userDetailsService;
#Autowired
private CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;
#Autowired
private DataSource dataSource;
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// Setting Service to find User in the database.
// And Setting PassswordEncoder
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure( HttpSecurity http ) throws Exception {
http.csrf().disable();
// /userInfo page requires login as ROLE_USER or ROLE_ADMIN.
// If no login, it will redirect to /login page.
http.authorizeRequests().antMatchers(
"/user/**")
.access("hasAnyRole('ROLE_USER', 'ROLE_ADMIN')");
// For ADMIN only.
http.authorizeRequests().antMatchers(
"/admin/**")
.access("hasRole('ROLE_ADMIN')");
// http.exceptionHandling().accessDeniedPage( "/error/403" );
// When the user has logged in as XX.
// But access a page that requires role YY,
// AccessDeniedException will be thrown.
http.authorizeRequests().and().exceptionHandling().accessDeniedPage("/public/403");
// Config for Login Form
http.authorizeRequests().and().formLogin()//
// Submit URL of login page.
.loginProcessingUrl("/j_spring_security_check") // Submit URL
.loginPage("/public/login")
.defaultSuccessUrl("/public/showAtlas")//
.successHandler( customAuthenticationSuccessHandler )
.failureUrl("/public/login?error=true")//
.usernameParameter("username")//
.passwordParameter("password")
//Config for Logout Page
.and()
.logout().logoutUrl("/public/logout").logoutSuccessUrl("/public/logoutSuccessful");
http.authorizeRequests().antMatchers(
"/public/**").permitAll();
// The pages does not require login
}
}
Assuming you wanted to add user to session on seccessful login, You can create the AuthenticationSuccessHandler like below and register using successHandler(new AuthenticationSuccessHandlerImpl())
Update:
If we create the object AuthenticationSuccessHandlerImpl, it will not be spring mananged and hence autowire into your Securityconfig and use it like shown below.
Here autowire the AuthenticationSuccessHandler in your WebSecurityConfig
#Autowired
AuthenticationSuccessHandler authenticationSuccessHandler;
and use it
WebSecurityConfig.java
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/resources/**", "/registration").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll().successHandler(authenticationSuccessHandler) // See here
.and()
.logout()
.permitAll();
}
The AuthenticationSuccessHandlerImpl.java
import java.io.IOException;
import java.security.Principal;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import com.techdisqus.auth.repository.UserRepository;
#Component
public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler{
#Autowired HttpSession session; //autowiring session
#Autowired UserRepository repository; //autowire the user repo
private static final Logger logger = LoggerFactory.getLogger(AuthenticationSuccessHandlerImpl.class);
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
// TODO Auto-generated method stub
String userName = "";
if(authentication.getPrincipal() instanceof Principal) {
userName = ((Principal)authentication.getPrincipal()).getName();
}else {
userName = ((User)authentication.getPrincipal()).getUsername();
}
logger.info("userName: " + userName);
//HttpSession session = request.getSession();
session.setAttribute("userId", userName);
}
}
Hope this helps.
Let me supplement above two solutions. My experience showed the following statement initiated below exception:
session.setAttribute("userId", userName);
Exception:
java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread?
And I was able to remove it after studying Using a request scoped bean outside of an actual web request. That is, I've overridden onStartup method in the class which extends AbstractAnnotationConfigDispatcherServletInitializer class.
#Override
public void onStartup(ServletContext servletContext)
throws ServletException {
super.onStartup(servletContext);
servletContext.addListener(new RequestContextListener());
}
Another approach: Registered a bean listening for Spring Security's InteractiveAuthenticationSuccessEvent and SessionDestroyedEvent events. These events fire without any explicit configuration in a Spring Boot app.
See https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#web.security:
The basic features you get by default in a web application are:
. . .
A DefaultAuthenticationEventPublisher for publishing authentication events.
Handling these events, you can add username as a session attribute immediately after a user logons and remove that attribute when the security session (security context) is destroyed:
#Component
public class SessionStoreUsernameAuthEventHandler {
#EventListener
public void audit(InteractiveAuthenticationSuccessEvent e) {
getSession().ifPresent(s -> s.setAttribute("username", e.getAuthentication().getName()));
}
#EventListener
public void audit(SessionDestroyedEvent e) {
getSession().ifPresent(s -> s.removeAttribute("username"));
}
private static Optional<HttpServletRequest> getCurrentRequest() {
return Optional.ofNullable(RequestContextHolder.getRequestAttributes())
.filter(ServletRequestAttributes.class::isInstance)
.map(ServletRequestAttributes.class::cast)
.map(ServletRequestAttributes::getRequest);
}
private static Optional<HttpSession> getSession() {
return getCurrentRequest().map(HttpServletRequest::getSession);
}
}

How to use Spring Security for multiple login scenarios with Java configuration?

I have a Spring Boot application. It has a welcome page which users select their login type and they're redirected to a login page and get their role based on their selection. Each login page provides authentication mechanisms with different external web services. I configured my security for a scenario, but how to do this for multiple scenarios? Should I do it with multiple security configs, or all configurations in the same security config? If so how?
SecurityConfig.java
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationProvider cap;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/welcomeX").hasAuthority("X_USER")
.and()
.formLogin()
.loginPage("/login")
.loginPage("/main")
.loginProcessingUrl("/welcome")
.permitAll()
.failureUrl("/login?error=true");
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(cap);
}
CustomAuthenticationProvider.java
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
private ExternalService externalService;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
Response resp = externalService.authenticate(username, password);
if (resp.isSuccess()) {
List<GrantedAuthority> grantedAuths = new ArrayList<>();
grantedAuths.add(new SimpleGrantedAuthority("X_USER"));
Authentication auth = new UsernamePasswordAuthenticationToken(username, password, grantedAuths);
return auth;
} else {
return null;
}
}
}
You can define multiple WebSecurityConfigurerAdapters in a #Configuration. http://docs.spring.io/spring-security/site/docs/3.2.x/reference/htmlsingle/#multiple-httpsecurity

custom login with JPA and spring security

I have been trying to authenticate my service by spring security. For that I am using filter as
This is the Security configuration I have been Using: In this configuration I am filtering all requests and permitting all , by authentication. For Authentication I have login page. In the login form I have use 'username' for email and 'password' for password for the attribute names respectively.
public class LoginAuthenticate extends WebSecurityConfigurerAdapter{
#Autowired
#Qualifier("userDetailsService")
MyUserDetailService userDetailsService;
#Autowired
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
System.out.println("reaching");
}
#Override
protected void configure(HttpSecurity http) throws Exception{
http.authorizeRequests()
.antMatchers("/resources/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.usernameParameter("username")
.passwordParameter("password")
.defaultSuccessUrl("/success")
.failureUrl("/")
.permitAll()
.and()
.logout()
.permitAll().and().csrf().disable();
}
#Bean
public PasswordEncoder passwordEncoder(){
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
This is my program structure
And for implementing UserDEtailService I did the following
This is my UserDetailsService which I overrided, I have autowired the repository to get the email address and password from the database[ using email as username] and then created a userdetails user object with the username and password and returned that object. When the use inputs the username and password in login page then I should be able to authenticate using the details with the details pulled from the database like the email and password of the user should match the email and password retrieved from the db.But the problem is I couldnt fire the authenticate method in spring security configuration:
#Service("userDetailsService")
public class MyUserDetailService implements UserDetailsService{
#Autowired
IUsers userrepo;
#Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
User user = userrepo.finduserByUserName(username);
if (user == null) {
return null;
}
List<GrantedAuthority> auth = AuthorityUtils
.commaSeparatedStringToAuthorityList("ROLE_USER");
if (username.equals("admin")) {
auth = AuthorityUtils
.commaSeparatedStringToAuthorityList("ROLE_ADMIN");
}
String password = user.getLoginPassword();
String Email = user.getEmailAddress();
System.out.println(password+ " "+ Email);
return new org.springframework.security.core.userdetails.User(Email,password,auth);
}
}
But My authentication function is not evaluating the user!
Can someone help me what is going wrong here. There are no errors to debug. I consoled out if reaching authentication function , but not reaching. I tried every possible thing on internet on this custom login with JPA but my effort didn't turn up anything to be fruitful.
In class LoginAuthenticate: remove #Autowired before void configure(AuthenticationManagerBuilder auth),
add annotation for this class
#Configuration
#EnableWebSecurity
In class MyUserDetailService.loadUserByUsername: change
if (user == null) {
return null;
}
to
if (user == null) {
throw new UsernameNotFoundException ("User not found");
}

Spring Security Active Directory LDAP Authentication without full name

Using Spring Security 3.2 I have configured ActiveDirectoryLdapAuthenticationProvider. I am able to authenticate using full name example sharon#mydomain.com but when I try to authenticate with just username 'sharon' I get the below error
2015-12-21_17:07:00.752 DEBUG o.s.s.l.a.a.ActiveDirectoryLdapAuthenticationProvider - authenticate - Processing authentication request for user: sharon
2015-12-21_17:07:00.793 DEBUG o.s.s.l.SpringSecurityLdapTemplate - searchForSingleEntryInternal - Searching for entry under DN '', base = 'dc=mydomain,dc=com', filter = '(&(objectClass=user)(userPrincipalName={0}))'
2015-12-21_17:07:00.793 INFO o.s.s.l.SpringSecurityLdapTemplate - searchForSingleEntryInternal - Ignoring PartialResultException
2015-12-21_17:07:00.794 DEBUG o.s.s.l.a.LdapAuthenticationProvider - authenticate - Processing authentication request for user: gdcadmin
2015-12-21_17:07:00.796 DEBUG o.s.s.l.a.BindAuthenticator - bindWithDn - Attempting to bind as cn=gdcadmin,cn=Users,dc=mydomain,dc=com,dc=springframework,dc=org
2015-12-21_17:07:00.796 DEBUG o.s.s.l.DefaultSpringSecurityContextSource - setupEnvironment - Removing pooling flag for user cn=gdcadmin,cn=Users,dc=mydomain,dc=com,dc=springframework,dc=org
2015-12-21_17:07:00.858 DEBUG o.a.m.f.codec.ProtocolCodecFilter - messageReceived - Processing a MESSAGE_RECEIVED for session 1
2015-12-21_17:07:00.859 DEBUG o.a.d.shared.asn1.ber.Asn1Decoder - decode - >>>==========================================
.....
.....
.....
015-12-21_17:07:00.905 DEBUG o.s.s.l.a.BindAuthenticator - handleBindException - Failed to bind as cn=gdcadmin,CN=Users,DC=mydomain,DC=com: org.springframework.ldap.AuthenticationException: [LDAP: error code 49 - cannot bind the principalDn.]; nested exception is javax.naming.AuthenticationException: [LDAP: error code 49 - cannot bind the principalDn.]
As per spring security document :
A user named "Sharon", for example, would then be able to authenticate
by entering either the username sharon or the full Active Directory
userPrincipalName, namely sharon#mydomain.com
my configuration
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
auth.eraseCredentials(false);
auth.ldapAuthentication().userDnPatterns("cn={0},CN=Users,DC=mydomain,DC=com");
}
#Bean
public ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(env.getProperty("mydomain.com"),
env.getProperty("ldap://hmidir01.mydomain.com:389/"));
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
provider.setUserDetailsContextMapper(userDetailsContextMapper);
return provider;
}
What is the mistake in my configuration.
You can implement in given ways :
1) Without persisting data into our database
WebSecurityConfig .java
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(<ldap-domain>,<ldap-url>);
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
#Bean
public LoggerListener loggerListener() {
return new LoggerListener();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin/**").hasAnyAuthority("ADMIN")
.antMatchers("/user/**").hasAnyAuthority("ADMIN", "USER")
.antMatchers("/rest/**", "/css/**", "/fonts/**", "/images/**", "/js/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/").failureUrl("/?error").successHandler("/home").permitAll()
.usernameParameter("emailId").passwordParameter("password")
.and()
.logout()
.logoutUrl("/logout").logoutSuccessUrl("/").permitAll()
.and()
.exceptionHandling().accessDeniedPage("/home")
.and()
.csrf()
.and()
.httpBasic();
}
}
2) With persisting data into our database
WebSecurityConfig.java
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin/**").hasAnyAuthority("ADMIN")
.antMatchers("/user/**").hasAnyAuthority("ADMIN", "USER")
.antMatchers("/rest/**", "/css/**", "/fonts/**", "/images/**", "/js/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/").failureUrl("/?error").successHandler("/home").permitAll()
.usernameParameter("emailId").passwordParameter("password")
.and()
.logout()
.logoutUrl("/logout").logoutSuccessUrl("/").permitAll()
.and()
.exceptionHandling().accessDeniedPage("/home")
.and()
.csrf()
.and()
.httpBasic();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)throws Exception {
auth
.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
}
#Bean
public ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(<ldap-domain>(null), <ldap-url>);
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
provider.setUserDetailsContextMapper(userDetailsContextMapper());
return provider;
}
#Bean
public UserDetailsContextMapper userDetailsContextMapper() {
return new AttributesLDAPUserDetailsContextMapper();
}
}
AttributesLDAPUserDetailsContextMapper.java
public class AttributesLDAPUserDetailsContextMapper implements UserDetailsContextMapper {
#Autowired
private UserService service;
private InetOrgPersonContextMapper ldapUserDetailsMapper = new InetOrgPersonContextMapper();
#Override
public UserDetails mapUserFromContext(DirContextOperations dirContextOperations, String userName, Collection<? extends GrantedAuthority> collection) {
InetOrgPerson userLdap = (InetOrgPerson) ldapUserDetailsMapper.mapUserFromContext(dirContextOperations, userName, collection);
User user = service.findOne(userLdap.getUsername());
if (user == null) {
user = new Usere();
user.setName(StringUtils.defaultString(userLdap.getDisplayName()).trim());
user.setEmailId(StringUtils.defaultString(userLdap.getUsername()).trim());
user.setdescription(StringUtils.defaultString(userLdap.getDescription()).trim());
user.setIsAdmin(false);
user.setIsEmployee(true);
service.save(user);
}
return new LdapSecuredUser(user);
}
#Override
public void mapUserToContext(UserDetails userDetails, DirContextAdapter dirContextAdapter) {
ldapUserDetailsMapper.mapUserToContext(userDetails, dirContextAdapter);
}
}
LdapSecuredUser.java
public class LdapSecuredUser extends User implements LdapUserDetails {
private static final long serialVersionUID = -8997460180274787521L;
public LdapSecuredUser(User user) {
if (user != null) {
this.setId(user.getId());
this.setEmailId(user.getEmailId());
this.setName(user.getName());
this.setdescription(user.getDescription());
this.setIsAdmin(user.getIsAdmin());
this.setIsEmployee(user.getIsEmployee());
}
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("USER"));
if(super.getIsAdmin())
authorities.add(new SimpleGrantedAuthority("ADMIN"));
return authorities;
}
#Override
public String getUsername() {
return super.getEmailId();
}
#Override
public String getPassword() {
return null;
}
#Override
public String getDn() {
return null;
}
#Override
public boolean isAccountNonExpired() {
return false;
}
#Override
public boolean isAccountNonLocked() {
return false;
}
#Override
public boolean isCredentialsNonExpired() {
return false;
}
#Override
public boolean isEnabled() {
return false;
}
}
With spring security 5.2.1:
You can use the setSearchFilter() function.
The LDAP auth process has two main steps: binding, which uses the domain parameter (the first) from ActiveDirectoryLdapAuthenticationProvider()to form the username like this: myUser#sub.domain.com
given credentials: username= myUser; password myPassword.
If this is is not correct, you'll get bad credential error (AcceptSecurityContext error, data 52e).
Then the next step is to finding your user in the ldap directory.
If your user doesn't have an attribute named username which is = myUser#sub.domain.com, the ldap server will give you back a not found error (this will pop up in your log as Ignoring PartialResultException from an UsernameNotFoundException: User myUser not found in directory. exception. For this you can use the searchFilter option.
The provided username will be inserted at the {1} point.
#Configuration
#EnableWebSecurity
#AllArgsConstructor
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception{
http.cors().and().csrf().disable().authorizeRequests()
.anyRequest().fullyAuthenticated().and().httpBasic(); //this will invoke an auth popup in browser
}
#Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(activeDirectoryLdapAuthenticationProvider());
}
#Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new
ActiveDirectoryLdapAuthenticationProvider("sub.domain.com","ldap://url");
provider.setSearchFilter("mailNickname={1}"); //here is the trick
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
}

Categories

Resources