My Introduction to Spring Security

This week at work, I've been working on migrating our application to containers to shift to the cloud.

We decided a good point to start would be with the Access Control application which takes care of the authentication and authorization of users to our application. Since we're a Java/Spring Boot shop, this was built upon Spring Security which led to down the rabbit hole of investigating/learning how Spring Security works and this blog page summarizes my learning.

A lot of the information in this book has been taken from [1] and I highly recommend the resource to learn in-detail about Spring Security!




Introduction

Before we dive into Spring Security, we need to understand the difference between Authentication and Authorization.

Authentication is proving who you say you are; the most common way this is done is with a username and a password.

Authorization on the other hand is about what services/pages you have access to following the authentication process, this is usually handled with roles assigned to users/groups.

Adding Spring Security

If we're using Spring Boot, we have a lot of auto configuration that's done for us and thus, just adding Spring Security dependency [3] gives us a good starting point.
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>
If we just have the above dependency and an endpoint defined as below:
@RestController
public class HelloController {

  @GetMapping("/hello")
  public String helloWorld() {
    return "Hello, World!";
  }
}
Once we start the application; we will see a password in the console that needs to be used to access the endpoint.
curl -u user:93a01cf0-794b-4b98-86ef-54860f36f7f3 http://localhost:8080/hello
Behind the scenes, cURL encodes the string <username>:<password> in Base64 and sends it as the value of the Authorization header prefixed with the string Basic.

Just by adding the dependency above, with the default configuration, we get 2 different authentication mechanisms:
  1. HTTP Basic
  2. Form Login
If we access the endpoint 'localhost:8080/hello' from the web browser, we will see the Form Login.

The image below shows an overview of the main components of Spring Security, it's been taken from [1].


InMemoryUserDetailsManager

InMemoryUserDetailsManager [4] implements the UserDetailsService to provide support for username/password based authentication that is stored in-memory.
@Bean
public UserDetailsService users() {
	UserDetails user = User.builder()
		.username("user")
		.password("12345")
		.roles("USER")
		.build();
	UserDetails admin = User.builder()
		.username("admin")
		.password("12345") 
		.roles("USER", "ADMIN")
		.build();
	return new InMemoryUserDetailsManager(user, admin);
}

PasswordEncoder

For the above listing, we also need to define a Password Encoder.
@Bean
public PasswordEncoder passwordEncoder() {
  return NoOpPasswordEncoder.getInstance();
}
NoOpPasswordEncoder doesn't encrypt or hash passwords so not recommended to use in anything beyond development/proof of concepts.

Customizing Authentication and Authorization

SecurityFilterChain

To customize authentication and authorization, we need to define a bean of type 'SecurityFilterChain'.
@Configuration
public class ProjectConfig {
  
  @Bean
  SecurityFilterChain configure(HttpSecurity http) 
    throws Exception {

    http.httpBasic(Customizer.withDefaults());

    http.authorizeHttpRequests(
      c -> c.anyRequest().authenticated()     
    );

    return http.build();  
  }

  // Omitted code
  
}
The above code allows responses to only authenticated requests.

In the code above, the 'http.httpBasic()' added the HTTP Basic authentication which we've been using in our curl commands.

The other method 'http.authorizeHttpRequests()' configures the authorization rules at the endpoint level.

Both the above methods used the 'Customizer' functional interface[5].

Another configuration process

In the example above, we created a CustomConfiguration object in which we have 2 methods that returned an UserDetailsService object and a PasswordEncoder object, we can setup the configuration to directly use the SecurityFilterChain to set both.
@Configuration
public class ProjectConfig {

  @Bean
  SecurityFilterChain configure(HttpSecurity http) 
    throws Exception {
 
    http.httpBasic(Customizer.withDefaults());

    http.authorizeHttpRequests(
        c -> c.anyRequest().authenticated()
    );

    var user = User.withUsername("john")
        .password("12345")
        .authorities("read")
        .build();

    var userDetailsService =
        new InMemoryUserDetailsManager(user);

    http.userDetailsService(userDetailsService);

    return http.build();
  }

  @Bean
  PasswordEncoder passwordEncoder() {
    return NoOpPasswordEncoder.getInstance();
  }
}

Defining custom authentication logic

We can define custom logic by implementing the 'AuthenticationProvider' and then provide this in the configuration class by injecting the 'CustomAuthenticationProvider' into the ProjectConfig class and passing it to the 'http.authenticationProvider(authenticationProvider)' method.

Separating Configurations

It is a good practice to separate the responsibilities for the configuration classes as shown below:
@Configuration
public class UserManagementConfig {

  @Bean
  public UserDetailsService userDetailsService() {
    var userDetailsService = new InMemoryUserDetailsManager();

    var user = User.withUsername("john")
                .password("12345")
                .authorities("read")
                .build();

    userDetailsService.createUser(user);
    return userDetailsService;
  }

  @Bean
  public PasswordEncoder passwordEncoder() {
    return NoOpPasswordEncoder.getInstance();
  }
}
@Configuration
public class WebAuthorizationConfig {

  @Bean
  SecurityFilterChain configure(HttpSecurity http) 
    throws Exception {

    http.httpBasic(Customizer.withDefaults());

    http.authorizeHttpRequests(
        c -> c.anyRequest().authenticated()
    );

    return http.build();
  }
}

Summary

This blog post is already getting too long and I haven't even gotten to the interesting parts (implementing OAuth/customize authorization with filters) yet which I'll post about coming soon!


Resources:




[4] https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/in-memory.html

[5] https://docs.spring.io/spring-security/reference/api/java/org/springframework/security/config/Customizer.html

[6] 

Comments

Popular posts from this blog

Playing around with Dell R520 server

Experience Interviewing for an Infrastructure Engineer - Continuous Delivery position

Plan for July 1st long weekend.