#
Authentication : InMemoryUserDetailsManager
This tutorial explains how we can authenticate into Spring Boot using Spring Security and credentials and roles stored in memory.
This tutorial consider you have already created the application from My first Spring Boot Service using Spring Security and disabled the CSRF.
Info
I use Maven, Java language, Spring Boot 3.1.1 version 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 memory.
The first question is "How we can get these credentials from memory" ?
For this, we need to define them "manually" in the code. This is done by the following code snippet:
@Bean
public UserDetailsService users() {
List<UserDetails> userDetailsList = new ArrayList<>();
User.UserBuilder builder = User.builder();
UserDetails userDan = builder
.username("dan")
.password(passwordEncoder().encode("dd"))
.roles("read")
.build();
UserDetails userAnna = builder
.username("anna")
.password(passwordEncoder().encode("aa"))
.roles("write")
.build();
UserDetails admin = builder
.username("admin")
.password(passwordEncoder().encode("adm"))
.roles("admin")
.build();
userDetailsList.add(userDan);
userDetailsList.add(userAnna);
userDetailsList.add(admin);
return new InMemoryUserDetailsManager(userDetailsList);
}
Here we have the whole configuration class:
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.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import java.util.ArrayList;
import java.util.List;
@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
public UserDetailsService users() {
List<UserDetails> userDetailsList = new ArrayList<>();
User.UserBuilder builder = User.builder();
UserDetails userDan = builder
.username("dan")
.password(passwordEncoder().encode("dd"))
.roles("read")
.build();
UserDetails userAnna = builder
.username("anna")
.password(passwordEncoder().encode("aa"))
.roles("write")
.build();
UserDetails admin = builder
.username("admin")
.password(passwordEncoder().encode("adm"))
.roles("admin")
.build();
userDetailsList.add(userDan);
userDetailsList.add(userAnna);
userDetailsList.add(admin);
return new InMemoryUserDetailsManager(userDetailsList);
}
@Bean
public PasswordEncoder passwordEncoder()
{
return new BCryptPasswordEncoder();
}
}
Info
In addition to the article named Store Users & Passwords into a MySql database this tutorial, uses an encoded password. Take a look at the
passwordEncoder()
method.InMemoryUserDetailsManager
class is an implementation ofUserDetailsService
Interface.We can also store the user credentials, the roles, etc into a LDAP server or a database. For retrieving the LDAP/database information we can use
LdapUserDetailsManager
/JdbcUserDetailsManager
classes which are implementations ofUserDetailsService
Interface.
Info
- PasswordEncoder is an interface for encoding passwords. The preferred implementation is BCryptPasswordEncoder.
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
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.
Info
As you can see, the authentication code is the same for this article and for article named Authentication : JdbcUserDetailsManager. The authentication is the same, even if the password now is encoded and the user details are taken from memory and not from a database.
The Controller of my application looks like:
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 !