#
Authentication : JdbcUserDetailsManager
This tutorial explains how we can authenticate into Spring Boot using Spring Security and credentials stored into a MySql database.
This tutorial consider you have already created the application from My first Spring Boot Service using Spring Security , disabled the CSRF and Store Users & Passwords into a MySql database.
Info
I use Maven, Java language, Spring Boot 3.1.1 version. I choose packaging method "jar", and Java 17.
In addition, of what we have got from the articles above, we need to add the authentication mechanism. In our example the username and the password are stored in MySql database for each user.
The first question is "How we can get these credentials from the database" ?
This is done automatically by the following code snippet:
@Bean
// We are using JdbcUserDetailsManager interface for finding the credentials
public UserDetailsService userDetailsService(DataSource dataSource) {
return new JdbcUserDetailsManager(dataSource);
}
Info
Don't forget that the Data source was defined in the article named Store Users & Passwords into a MySql database.
We can also define the user credentials, the roles, etc using
InMemoryUserDetailsManager
which is an implementation ofUserDetailsService
.We can also store the user credentials, the roles, etc into a LDAP server. For retrieving the LDAP information we can use
LdapUserDetailsManager
which is an implementation ofUserDetailsService
.
Here we have the whole configuration class which implements the Spring Boot Authentication:
package com.demo.springsecurity.config;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class ProjectSpringSecurityConfig {
@Bean
public SecurityFilterChain myFilterChain1(HttpSecurity http) throws Exception {
// We have a Basic authentication (username & password)
http.httpBasic(Customizer.withDefaults())
// CSRF is disabled
.csrf(csrf -> csrf.disable())
// Only authenticated requests are allowed for URL pattern "/employee/*"
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/employee/*").authenticated()
);
return http.build();
}
@Bean
// We are using JdbcUserDetailsManager interface for finding the credentials
public UserDetailsService userDetailsService(DataSource dataSource) {
return new JdbcUserDetailsManager(dataSource);
}
@Bean
// We are not hashing the password : the password is in clear text in the MySql database
// This mustn't happen in PROD environment, but it is good for testing purpose
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
Info
- PasswordEncoder is an interface for encoding passwords. The preferred implementation is BCryptPasswordEncoder.
- In my example the password is not encoded.
The Security Filter Chain let only the authenticated user to pass through it. Unauthenticated users will receive "401 Unauthorized" message.
The authentication itself, is done by the implementation of AuthenticationProvider
:
package com.demo.springsecurity.component;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
@Component
public class MyAuthenticationProvider implements AuthenticationProvider {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (passwordEncoder.matches(password, userDetails.getPassword())) {
return new UsernamePasswordAuthenticationToken(
username,
password,
userDetails.getAuthorities()
);
} else {
throw new BadCredentialsException("Something went wrong with the Authentication");
}
}
@Override
// Return true if this AuthenticationProvider supports the provided authentication class
public boolean supports(Class<?> authenticationType) {
return authenticationType.equals(UsernamePasswordAuthenticationToken.class);
}
}
Info
- The authentication is done by
authenticate()
method of the AuthenticationProvider implementation. This method receive as input an Authentication object and return the same object type, but with more information about the authenticated user. - The authentication process uses the information stored in UserDetailsService and PasswordEncoder implementations.
- We need also to specify what kind of authentication type this Authentication Provider is using.
For this, we need to implement/override the
supports()
method.
I have also modified the Controller to look like this:
package com.demo.springsecurity.controller;
import com.demo.springsecurity.model.Employee;
import com.demo.springsecurity.service.EmployeeService;
import com.google.gson.Gson;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
@RestController
@RequestMapping("/employee")
public class EmployeeController {
@Autowired
EmployeeService employeeService;
@Autowired
private ApplicationContext context;
@GetMapping(value="/all")
String getAllEmployees() {
ArrayList<Employee> allEmployees = employeeService.getEmployees();
String json = new Gson().toJson(allEmployees);
return json;
}
@GetMapping(value="/info")
String info() {
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication a = securityContext.getAuthentication();
var returnVar = "Authenticated with : "+a.getName();
return returnVar;
}
@PutMapping (value="/add", consumes = "application/json")
int addEmployee(@RequestBody Employee newEmployee) {
employeeService.addEmployee(newEmployee);
int newCount = employeeService.countEmployees();
return newCount;
}
@DeleteMapping(value="/delete", consumes = "application/json")
int deleteEmployee(@RequestBody String empId) {
employeeService.removeEmployee(empId);
int newCount = employeeService.countEmployees();
return newCount;
}
}
You can see how we can get authentication information from the Authentication object (after the authentication of the user). All this information is available in the Security Context Holder:
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication a = securityContext.getAuthentication();
var returnVar = "Authenticated with : "+a.getName();
Enjoy Spring Security Authentication !