SpringSecurity : Authenticate User with Custom UserDetailsService

Points To Remember

  1. Create class that implement UserDetailsService and override loadUserByUsername() method.
  2. Throw UsernameNotFoundException if no user was found by username.
  3. 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

  1. user
  2. role
  3. 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


    Download from Github

No comments:

Powered by Blogger.