Spring Security with DB Authentication

Steps :

  1. Create DB Table that will support your authentication
  2. Create User Bean with all the required fields.
  3. Create a @Repository Bean that creates the User Bean using username
  4. Test step 1 and 2 by directly calling the repository via a controller
  5. Create implementation of UserDetails interface (UserPrincipal) using decorator pattern
  6. Create implementation of UserDetailsService interface (UserPrincipalDetailsService)
  7. Hook the new Authentication provider created into the implementation of WebSecurityConfigurerAdapter class
  8. Test the implementation
    • This implementation almost 90% does not work at the first attempt so debug will most effeminately will be required, below points shall help
    • Check if the username is getting correctly passed into loadUserByUsername() method
    • Check base 64 encoding
    • Check the password encoder setting
    • Check the logs
    • Disable the CSRF

Step 1 : Create DB Table that will support your authentication

create table emp_temp
(
EMP_ID number(2),
EMP_USERNAME varchar2(10),
EMP_PASS varchar2(10),
EMP_ROLES varchar2(50),
EMP_PERMISSIONS varchar2(50)
);


insert into emp_temp values (1,'Tyson','123','ADMIN','');
insert into emp_temp values (2,'Justin','123','USER','');
insert into emp_temp values (3,'Martin','123','ADMIN','MANAGER');
insert into emp_temp values (4,'Jake'',123','USER','MANAGER');
insert into emp_temp values (5,'Duke','123','ADMIN','LEAD');
insert into emp_temp values (6,'Fade'',123','USER','LEAD');

Step 2 : Create User Bean with all the required fields.

User.java

package com.springSecurity.user;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class User {
	
	private String id;
	private String username;
	private String password;
	private String roles;
	private String permissions;
	
	public User() {
		
	}
	public User(String id, String username, String password, String roles, String permissions) {
		this.id = id;
		this.username = username;
		this.password = password;
		this.roles = roles;
		this.permissions = permissions;
	}
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	public String getRoles() {
		return roles;
	}
	public void setRoles(String roles) {
		this.roles = roles;
	}
	public String getPermissions() {
		return permissions;
	}
	public void setPermissions(String permissions) {
		this.permissions = permissions;
	}
	public List<String> getRolesList(){
		List<String> arrayList = new ArrayList<>(); 
		if(this.roles.length()>0) {
			Collections.addAll(arrayList,this.roles.split(",")); 
		}
		return arrayList;
	}
	
	public List<String> getPermissionsList(){
		List<String> arrayList = new ArrayList<>(); 
		if(this.roles.length()>0) {
			Collections.addAll(arrayList,this.permissions.split(",")); 
		}
		return arrayList;
	}
}

Step 3 : Create a @Repository Bean that creates the User Bean using username

UserDAO.java

package com.springSecurity.user;

import java.sql.ResultSet;
import java.sql.SQLException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;

@Repository
public class UserDAO {
	
	@Autowired
	JdbcTemplate jdbcTemplate;
	
	public User getEmpDetails(String userName) {
		StringBuilder query = new StringBuilder();
		query.append("select EMP_ID, EMP_USERNAME, EMP_PASS, EMP_ROLES, EMP_PERMISSIONS from emp_temp where EMP_USERNAME=?");
		User user =jdbcTemplate.queryForObject(query.toString(),new Object[] {userName}, new RowMapper<User>() {

			@Override
			public User mapRow(ResultSet rs, int rowNum) throws SQLException {
				return new User(
						rs.getString("EMP_ID"),
						rs.getString("EMP_USERNAME"),
						rs.getString("EMP_PASS"),
						rs.getString("EMP_ROLES"),
						rs.getString("EMP_PERMISSIONS"));
			}
			
		});
		return user;
	}
}

Step 4 :Test step 1 and 2 by directly calling the repository via a controller

Controller File

package com.springSecurity;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.springSecurity.user.User;
import com.springSecurity.user.UserService;

@RestController
public class DBController {
	
	@Autowired
	JdbcTemplate jdbcTemplate;
	
	@Autowired
	UserService userService;	

	@PostMapping("/empList")
	public User getEmpList2(@RequestBody Map<String,String> data){
		return userService.getEmpDetails(data.get("loginId"));
	}
}

Service File

package com.springSecurity.user;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {
	
	@Autowired
	UserDAO userDAO;
	
	public User getEmpDetails(String userName) {
		return userDAO.getEmpDetails(userName);
	}
}

Test Using Rest Tool

Step 5 : Create implementation of UserDetails interface (UserPrincipal) using decorator pattern

UserPrincipal.java

package com.springSecurity.user;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

public class UserPrincipal implements UserDetails{
	
	private static final long serialVersionUID = 1L;

	private User user;
	
	public UserPrincipal(User user) {
		this.user = user;
	}

	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		List<GrantedAuthority> authorityList = new ArrayList<>();
		this.user.getPermissionsList().forEach(p -> {
			GrantedAuthority authority = new SimpleGrantedAuthority(p);
			authorityList.add(authority);
		});
		
		this.user.getRolesList().forEach(p -> {
			GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_"+p);
			authorityList.add(authority);
		});
		
		return authorityList;
	}

	@Override
	public String getPassword() {
		return user.getPassword();
	}

	@Override
	public String getUsername() {
		return user.getUsername();
	}

	@Override
	public boolean isAccountNonExpired() {
		return true;
	}

	@Override
	public boolean isAccountNonLocked() {
		return true;
	}

	@Override
	public boolean isCredentialsNonExpired() {
		return true;
	}

	@Override
	public boolean isEnabled() {
		return true;
	}

}

Step 6 : Create implementation of UserDetailsService interface (UserPrincipalDetailsService)

UserPrincipalDetailsService.java

package com.springSecurity.user;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
public class UserPrincipalDetailsService implements UserDetailsService{

	@Autowired
	UserDAO userDAO;
	
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		User user = userDAO.getEmpDetails(username);
		UserPrincipal userPrincipal = new UserPrincipal(user);
		return userPrincipal;
	}
}

Step 7 : Hook the new Authentication provider created into the implementation of WebSecurityConfigurerAdapter class

SecurityConfig.java

package com.springSecurity;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import com.springSecurity.user.UserPrincipalDetailsService;

@Configuration
@EnableWebSecurity
@ConditionalOnProperty (name = "myproject.security.enabled", havingValue = "true", matchIfMissing = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter{

	@Autowired
	UserPrincipalDetailsService userPrincipalDetailsService;
	
	/**@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth
			.inMemoryAuthentication()
			.withUser("admin1").password(passwordEncoder().encode("admin")).roles("ADMIN").and()
			.withUser("admin2").password(passwordEncoder().encode("admin")).roles("ADMIN").authorities("MANAGER").and()
			.withUser("user1").password(passwordEncoder().encode("user")).roles("USER").and()
			.withUser("user2").password(passwordEncoder().encode("user")).roles("USER").authorities("LEAD");
	}*/
	
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.authenticationProvider(authenticationProvider());
	}
	
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		
		http.csrf().disable();
		
		http
			.authorizeRequests()
			.antMatchers("/").permitAll()//bypass authetication and authorization
			.antMatchers("/empList").permitAll()//bypass authetication and authorization
			.antMatchers("/empList2").permitAll()//bypass authetication and authorization
			.antMatchers("/profile").authenticated()//Authentication only required
			.antMatchers("/user/**").hasRole("USER")//Authetication and Authorization required
			.antMatchers("/admin/**").hasRole("ADMIN")//Authetication and Authorization required
			.antMatchers("/useroradmin").hasAnyRole("ADMIN","USER")//Either admin or user role required
			//permissions
			.antMatchers("/listTeam").hasAuthority("LEAD")//Authetication, Authorization (since /user/** is required) and permission required
			.antMatchers("/listEmp").hasAuthority("MANAGER")//Authetication, Authorization and permission required
			.antMatchers("/**").denyAll()
			.and()
			.httpBasic();
		http
			.sessionManagement() 
			.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
	}
	
	@Bean
	DaoAuthenticationProvider authenticationProvider() {
		DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
		daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
		daoAuthenticationProvider.setUserDetailsService(userPrincipalDetailsService);
		return daoAuthenticationProvider;
	}
	
	@Bean
	PasswordEncoder passwordEncoder() {
		return NoOpPasswordEncoder.getInstance();
//		return new BCryptPasswordEncoder();
	}
	
}

Step 8 : Test the implementation

Below is the sample controller used for testing

package com.springSecurity;

import java.util.ArrayList;
import java.util.List;

import javax.annotation.security.RolesAllowed;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class Controller {
	
	/**
	 * Accessable without Authentication and authorization
	 * @return
	 */
	@GetMapping("/")
	public String getWelcomePage() {
		return "Hello....welcome to our website";
	}
	
	/**
	 * All logged in users
	 * Accessable with authentication but authorization not required
	 * @return
	 */
	@GetMapping("/profile")
	public String getProfile() {
		return "Welcome to profile page";
	}
	
	/**
	 * Only logged in user with role USER can access this
	 * @return
	 */
	@GetMapping("/user")
	public String getUser() {
		return "Hello User Role";
	}
	
	/**
	 * Only loggged in admin can access this
	 * @return
	 */
	@GetMapping("/admin")
	public String getAdmin() {
		return "Hello Admin Role";
	}
	
	/**
	 * Either User or Admin
	 * @return
	 */
	@GetMapping("/useroradmin")
	public String getUserOrAdmin() {
		return "Hello User or Admin Role";
	}
	
	@GetMapping("/listTeam")
	public List<String> listTeam() {
		List<String> list = new ArrayList();
		list.add("Team Memeber 1");
		list.add("Team Memeber 2");
		list.add("Team Memeber 3");
		return list;
	}
	
	@GetMapping("/listEmp")
	public List<String> listEmp() {
		List<String> list = new ArrayList();
		list.add("Team Memeber 1");
		list.add("Team Memeber 2");
		list.add("Team Memeber 3");
		list.add("Team Lead 1");
		list.add("Team Lead 2");
		return list;
	}
}

Get the UserDetails from the Spring Security using below steps :

		Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();

		if (principal instanceof UserDetails) {
		  username = ((UserDetails)principal).getUsername();
		} else {
		  username = principal.toString();
		}

Reference :

Youtube : Part 1 , Part 2

Code :

GitHub : Branch – spring-security-db-authentication

Leave a Comment