Spring Security is responsible for authenticate and authorize Spring applications, it provide multiple way of doing authentication but Basic is probably the simplest.
With basic authentication, for every request made to the api, we send the user credentials in the headers, generally encoded with Base64 format.
Creating the Spring Boot application
We keep it simple with only spring web and spring security.
We will add a simple GET request:
package com.example.spring_basic;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DemoController {
@GetMapping("/hello")
public String hello(){
return "hello world !";
}
}
If we run the application and we inspect the logs, we can see that Spring is already handling security with a generated password
We can already test the request, for instance with IntelliJ Http request, and see that it works:
### GET request to example server
GET http://localhost:8080/hello
Authorization: Basic user <Generated password>
However this is not what we want, so let's add a new Spring Security Configuration:
package com.example.spring_basic;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import static org.springframework.security.config.Customizer.withDefaults;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity.httpBasic(withDefaults());
httpSecurity.authorizeHttpRequests(http ->{
http.requestMatchers("/users").permitAll();
http.anyRequest().authenticated();
});
return httpSecurity.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public InMemoryUserDetailsManager inMemoryUserDetailsManager() {
UserDetails user= User.builder().username("user")
.password(passwordEncoder().encode("password"))
.roles("USER").build();
UserDetails admin=User.builder().username("admin")
.password(passwordEncoder().encode("password"))
.roles("USER","ADMIN").build();
return new InMemoryUserDetailsManager(user, admin);
}
}
First we recreate a SecurityFilterChain bean, for now we only add basic auth and require all requests to be authenticated.
Then we declare a PasswordEncoder bean that will be used to encode the password during the security process.
Then we declare a UserDetails implementation. For the moment we will not create custom UserDetails and User. You can see that we use the passwordEncoder to encode the "password" value. So when we will compare the provided password with the user password we will compare the encoded version of the password.
We can test the request by providing credentials:
### GET request to example server
GET http://localhost:8080/hello
Authorization: Basic user password
It should works, but how ?
Behind the scene
When we make the request, it flows through a filter chain with differents filters, when a filter detects the headers he wants, he can call the AuthenticationManager. In our case, when the BasicAuthenticationFilter receive a request with username and password, it call the AuthenticationManager with a non valided UsernamePasswordToken.
The AuthenticationManager will then find a provider that handle this token, in our case the DaoAuthenticationProvider. It will use the UserDetails to find User with the same username as the credentials, then encode the password and compare it with the credentials password. If it matchs, it will update the token, add it to the SecurityContext, and return the token to the manager, then to the filter.
You can find the source code here
Top comments (0)