SpringSecurity : Authenticate User with Custom UserDetailsService

Points To Remember


  • Create class that implement UserDetailsService and override loadUserByUsername() method.
  • Throw UsernameNotFoundException if no user was found by username.
  • Register this class as a bean by overriding WebSecurityConfigurerAdapter.

Authenticate User with Custom UserDetailsService

Step 1 : Create Entities for User and Role

  • Create Entity User
    1. package com.ekiras.ss.domain;  
    2.   
    3. import javax.persistence.*;  
    4. import java.util.Set;  
    5.   
    6. /** 
    7.  * @author ekansh 
    8.  * @since 2/4/16 
    9.  */  
    10. @Entity  
    11. public class User {  
    12.   
    13.     @Id  
    14.     @GeneratedValue(strategy = GenerationType.AUTO)  
    15.     private long id;  
    16.   
    17.     private String username;  
    18.   
    19.     private String password;  
    20.   
    21.     private boolean enabled;  
    22.   
    23.     @ManyToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL)  
    24.     @JoinTable(joinColumns = @JoinColumn(name = "user_id"),inverseJoinColumns = @JoinColumn(name = "role_id"))  
    25.     private Set<Role> roles;  
    26.   
    27.     // GETTERS and SETTERS  
    28. }  
  • Create Entity Role
    1. package com.ekiras.ss.domain;  
    2.   
    3. import javax.persistence.*;  
    4. import java.util.Set;  
    5.   
    6. /** 
    7.  * @author ekansh 
    8.  * @since 2/4/16 
    9.  */  
    10. @Entity  
    11. public class Role {  
    12.   
    13.     @Id  
    14.     @GeneratedValue(strategy = GenerationType.AUTO)  
    15.     private long id;  
    16.   
    17.     private String role;  
    18.   
    19.     @ManyToMany(mappedBy = "roles",fetch = FetchType.LAZY)  
    20.     private Set<User> users;  
    21.   
    22.     // GETTERS and SETTERS  
    23. }  
This will create three tables in your database

  • user
  • role
  • user_roles 

Step 2 : Create CustomUserDetailsService


Now, create a new class named as SSUserDetailsService that implements UserDetailsService and override the loadUserByUsername() method  as follows.

  1. package com.ekiras.ss.config;  
  2.   
  3. import com.ekiras.ss.domain.Role;  
  4. import com.ekiras.ss.domain.User;  
  5. import com.ekiras.ss.repository.UserRepository;  
  6. import org.slf4j.Logger;  
  7. import org.slf4j.LoggerFactory;  
  8. import org.springframework.security.core.GrantedAuthority;  
  9. import org.springframework.security.core.authority.SimpleGrantedAuthority;  
  10. import org.springframework.security.core.userdetails.UserDetails;  
  11. import org.springframework.security.core.userdetails.UserDetailsService;  
  12. import org.springframework.security.core.userdetails.UsernameNotFoundException;  
  13.   
  14. import javax.transaction.Transactional;  
  15. import java.util.HashSet;  
  16. import java.util.Set;  
  17.   
  18. /** 
  19.  * @author ekansh 
  20.  * @since 2/4/16 
  21.  */  
  22. @Transactional  
  23. public class SSUserDetailsService implements UserDetailsService {  
  24.   
  25.     private static final Logger LOGGER = LoggerFactory.getLogger(SSUserDetailsService.class);  
  26.   
  27.     private UserRepository userRepository;  
  28.   
  29.     public SSUserDetailsService(UserRepository userRepository){  
  30.         this.userRepository=userRepository;  
  31.     }  
  32.   
  33.     @Override  
  34.     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {  
  35.         try {  
  36.             User user = userRepository.findByUsername(username);  
  37.             if (user == null) {  
  38.                 LOGGER.debug("user not found with the provided username");  
  39.                 return null;  
  40.             }  
  41.             LOGGER.debug(" user from username " + user.toString());  
  42.             return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), getAuthorities(user));  
  43.         }  
  44.         catch (Exception e){  
  45.             throw new UsernameNotFoundException("User not found");  
  46.         }  
  47.     }  
  48.   
  49.     private Set<GrantedAuthority> getAuthorities(User user){  
  50.         Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();  
  51.         for(Role role : user.getRoles()) {  
  52.             GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(role.getRole());  
  53.             authorities.add(grantedAuthority);  
  54.         }  
  55.         LOGGER.debug("user authorities are " + authorities.toString());  
  56.         return authorities;  
  57.     }  
  58.   
  59.   
  60. }  

This class will load the User from username. When the login form is submitted, spring security will use this class to load the user by the username provided. If the User is not found with that name then the UsernameNotFoundException is thrown.

Note ::
loadUserByUsername() method should return an object of UserDetails. You can user two constructors to return this object

  • org.springframework.security.core.userdetails.User class
  • Object of any other class that implements UserDetails interface.

Step 3 : Register our Custom UserDetailsService to SpringSecurity as a Bean


Now, we need to register this class as a bean and tell spring security to use this class for loading user from database. So, first we will override the userDetailsBean
  1. @Autowired private UserRepository userRepository;  
  2.   
  3. @Override  
  4. public UserDetailsService userDetailsServiceBean() throws Exception {  
  5.     return new SSUserDetailsService(userRepository);  
  6. }  

Note :: We will pass the bean UserRepository to the UserDetailsService because this bean will be loaded before the repository is loaded, so we might get the useeRepository bean as null in out SSUserDetailsService.

Now we will configure spring security to use our UserDetailsService as follows
  1. @Override  
  2. protected void configure(AuthenticationManagerBuilder auth) throws Exception {  
  3.     auth.userDetailsService(userDetailsServiceBean());  
  4. }  


So, our spring security configurer may look like as shown in code below.
  1. package com.ekiras.ss.config;  
  2.   
  3. import com.ekiras.ss.repository.UserRepository;  
  4. import org.springframework.beans.factory.annotation.Autowired;  
  5. import org.springframework.context.annotation.Configuration;  
  6. import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;  
  7. import org.springframework.security.config.annotation.web.builders.HttpSecurity;  
  8. import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;  
  9. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;  
  10. import org.springframework.security.core.userdetails.UserDetailsService;  
  11. import org.springframework.security.web.util.matcher.AntPathRequestMatcher;  
  12.   
  13. /** 
  14.  * @author ekansh 
  15.  * @since 30/3/16 
  16.  */  
  17. @Configuration  
  18. @EnableWebSecurity  
  19. public class SpringSecurityConfigurer extends WebSecurityConfigurerAdapter{  
  20.   
  21.     @Autowired private UserRepository userRepository;  
  22.   
  23.     @Override  
  24.     protected void configure(AuthenticationManagerBuilder auth) throws Exception {  
  25.         auth.userDetailsService(userDetailsServiceBean());  
  26.     }  
  27.   
  28.     @Override  
  29.     public UserDetailsService userDetailsServiceBean() throws Exception {  
  30.         return new SSUserDetailsService(userRepository);  
  31.     }  
  32.   
  33.     @Override  
  34.     protected void configure(HttpSecurity http) throws Exception {  
  35.         http  
  36.             .authorizeRequests()  
  37.                 .antMatchers("/admin/**").hasAuthority("ADMIN")  
  38.                 .antMatchers("/user/**").hasAuthority("USER")  
  39.                 .anyRequest().authenticated()  
  40.             .and()  
  41.             .formLogin()  
  42.             .and()  
  43.             .logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/login");  
  44.         ;  
  45.     }  
  46.   
  47.   
  48.   
  49.   
  50.   
  51. }  

No comments :

Post a Comment