SpringSecurity : Authenticate User with Custom UserDetailsService
Points To Remember
- Create class that implement
UserDetailsService
and overrideloadUserByUsername()
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
package com.ekiras.ss.domain;
import javax.persistence.*;
import java.util.Set;
/**
* @author ekiras
*/
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String username;
private String password;
private boolean enabled;
@ManyToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
@JoinTable(joinColumns = @JoinColumn(name = "user_id"),inverseJoinColumns = @JoinColumn(name = "role_id"))
private Set<role> roles;
// GETTERS and SETTERS
}
Create Entity Role
package com.ekiras.ss.domain;
import javax.persistence.*;
import java.util.Set;
/**
* @author ekiras
*/
@Entity
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String role;
@ManyToMany(mappedBy = "roles",fetch = FetchType.LAZY)
private Set<user> users;
// GETTERS and SETTERS
}
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.
package com.ekiras.ss.config;
import com.ekiras.ss.domain.Role;
import com.ekiras.ss.domain.User;
import com.ekiras.ss.repository.UserRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import javax.transaction.Transactional;
import java.util.HashSet;
import java.util.Set;
/**
* @author ekiras
*/
@Transactional
public class SSUserDetailsService implements UserDetailsService {
private static final Logger LOGGER = LoggerFactory.getLogger(SSUserDetailsService.class);
private UserRepository userRepository;
public SSUserDetailsService(UserRepository userRepository){
this.userRepository=userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
try {
User user = userRepository.findByUsername(username);
if (user == null) {
LOGGER.debug("user not found with the provided username");
return null;
}
LOGGER.debug(" user from username " + user.toString());
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), getAuthorities(user));
}
catch (Exception e){
throw new UsernameNotFoundException("User not found");
}
}
private Set<grantedauthority> getAuthorities(User user){
Set<grantedauthority> authorities = new HashSet<grantedauthority>();
for(Role role : user.getRoles()) {
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(role.getRole());
authorities.add(grantedAuthority);
}
LOGGER.debug("user authorities are " + authorities.toString());
return authorities;
}
}
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
@Autowired private UserRepository userRepository;
@Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return new SSUserDetailsService(userRepository);
}
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
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsServiceBean());
}
So, our spring security configurer may look like as shown in code below.
package com.ekiras.ss.config;
import com.ekiras.ss.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
/**
* @author ekiras
*/
@Configuration
@EnableWebSecurity
public class SpringSecurityConfigurer extends WebSecurityConfigurerAdapter{
@Autowired private UserRepository userRepository;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsServiceBean());
}
@Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return new SSUserDetailsService(userRepository);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin/**").hasAuthority("ADMIN")
.antMatchers("/user/**").hasAuthority("USER")
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/login");
;
}
}
Also Read
- Configure Spring Security with Spring boot
- Configure JDBC Authetication using MYSQL Query
- Authenticate User with Custom UserDetailsService
- Implement Role Hierarchy with In-Memory Authentication
- How to list the User Authorities in Controller,Filter and Services
- Disable Session Creation for Stateless Authentication
Download from Github
No comments: