Secure REST API with JWT

Json Web Token – How to Secure REST API Using JWT in Spring Boot

This is a tutorial on how to implement Json Web Token authentication for a REST API in Spring Boot using public and private keys. In this approach, we would be using the SecurityFilterChain bean rather than extending the WebSecurityConfigurerAdapter.

Then we would be creating a self-signed JWT.

  1. Getting Started With Json Web Token( JWT)
  2. Create and Configure the SecurityFilterChain Bean
  3. Create a Test UserManager Bean
  4. Configure the ResourceServer
  5. Create a Public/Private Key Pair
  6. Create some Configuration to Externalize the Keys
  7. Create the JwtDecoder and JwtEncoder
  8. Create the TokenService
  9. Implement the “/token” Endpoint

Video Tutorial – The New JWT – How to Secure REST API Using Json Web Token in Spring Boot

1. Getting Started With JWT

We would start with a basic REST API. Then we would see how to secure an endpoint using Json Web Token.

You will need the following dependencies:

  • spring-boot-starter-oauth2-resource-server
  • spring-boot-starter-web
  • spring-boot-configuration-processor

How it works

  • For the first time, the user logs in with his username and password.
  • At successful authentication (login), the server generates and returns back a token (jwt) to the user
  • For subsequent requests, the token must be included in the request header

 

2. Create and Configure the SecurityFilterChain Bean

1. Create the security configuration file (name it SecurityConfig.java) in the config directory.

2. Annotate this class with @Configuration and @EnableWebSecurity

3. Create the SecurityFilterChain bin. It takes HttpSecurity as parameter, builds and returns the SecurityFilterChain.  The code below shows what is returned:

return httpSecurity
        .csrf(AbstractHttpConfigurer::disable)
        .authorizeHttpRequests(auth -> auth
                .anyRequest().authenticated()
        )
        .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .httpBasic(Customizer.withDefaults())
        .build();

 

3. Create a Test User Bean

For this tutorial, we would create a user in memory. In the following tutorial, we would implemented UserDetailsService.

1. Create the InMemoryUserDetailsManager bean as shown below:

@Bean
public InMemoryUserDetailsManager manager(){
    return new InMemoryUserDetailsManager(
            User.withUsername("kindson")
                    .password("{noop}password")
                    .authorities("read")
                    .build()
    );
}

 

4. Configure the ResourceServer

An OAuth2 resource server in Spring is a server that protects resources (e.g., APIs) and ensures that requests are authenticated using access tokens, typically issued by an OAuth2 authorization server.

We would have to tell Spring that our service is an OAuth2 resource server.

1. Add this line just before the sessionManagement line in the SecurityFilterChain bean

.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())) 

 

2. Run the application. Note that you will have the exception below:

Parameter 0 of method setFilterChains in org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration required a bean of type 'org.springframework.security.oauth2.jwt.JwtDecoder' that could not be found.

To create a JwtDecoder, we would need to create a public/private key pair.

 

5. Create a Public/Private Key Pair

We would use the openssl tool in Mac to create the keys

1. Create a folder called certs in the resources folder

2. Navigate to this certs directory in the terminal and run the following command

openssl genrsa -out keypair.pem 2048

 

3. Run the following command to extract the public key.

openssl rsa -in keypair.pem -pubout -out public.pem

 

4. Run the following command to create a private key in the required format: pkcs8

openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in keypair.pem -out private.pem

 

5. Delete the keypair.pem

 

6. Create Some Configuration to Externalize the Keys

1. Create a  record and call it RsaKeyProperties. This would have two fields as shown below:

@ConfigurationProperties(prefix = "rsa")
public record RsaKeyProperties(RSAPublicKey publicKey, RSAPrivateKey privateKey) {
}

 

2. Set the values in the application.properties file

rsa.private-key=classpath:certs/private.pem
rsa.public-key=classpath:certs/public.pem

 

3. Annotate the main class with @EnableConfigurationProperties like so

@EnableConfigurationProperties(RsaKeyProperties.class)

 

7. Create the JwtDecoder and JwtEncoder Bean

We would need the keys to be able to create a JwtDecoder. This would be used to decipher the token

1. Inject the RsaKeyProperties into the SecurityConfig as shown:

private final RsaKeyProperties properties;

public SecurityConfig(RsaKeyProperties properties) {
    this.properties = properties;
}

 

2. Defined the decoder using the keys:

@Bean
JwtDecoder jwtDecoder() {
    return NimbusJwtDecoder.withPublicKey(properties.publicKey()).build();
}

 

3. Create the JwtEncoder Bean

@Bean
JwtEncoder jwtEncoder(){
    JWK jwk = new RSAKey.Builder(rsaKeyProperties.publicKey()).privateKey(rsaKeyProperties.privateKey()).build();
    JWKSource<SecurityContext> jwkSource = new ImmutableJWKSet<>(new JWKSet(jwk));
    return new NimbusJwtEncoder(jwkSource);
}

 

8. Create the TokenService

We would need a service that contains the generateToken method. The method would create a token and encode it using the JwtEncoder.

1. Create the TokenService inside the service package

2. Get a reference to the JwtEncoder bean

3. Create the method as shown below:

public String generateToken(Authentication authentication){
    Instant now = Instant.now();
    String scope = authentication.getAuthorities().stream()
            .map(GrantedAuthority::getAuthority)
            .collect(Collectors.joining(" "));
    JwtClaimsSet claimsSet = JwtClaimsSet.builder()
            .issuer("self")
            .issuedAt(now)
            .expiresAt(now.plus(1, ChronoUnit.HOURS))
            .claim("scope", scope)
            .build();
    return this.jwtEncoder.encode(JwtEncoderParameters.from(claimsSet)).getTokenValue();
}

Note: See the video tutorial to understand each section of the code

 

9. Create the AuthController

We would need the controller that exposes an endpoint of “/token”. This would call the createToken service and return the created token.

1. Create the AuthController.java file in the controller package

2. Inject the TokenService.

3. Optionally create a Logger to log debug messages.

4. Write the getToken controller method as shown below:

@PostMapping("/token")
public String getToken(Authentication authentication){
    LOGGER.debug("Token requested form user: {}", authentication.getName());
    String token = tokenService.generateToken(authentication);
    LOGGER.debug("Token granted {}", token);
    return token;    }
}

 

Video Tutorial – The New JWT – How to Secure REST API Using Json Web Token in Spring Boot

kindsonthegenius

Kindson Munonye is currently completing his doctoral program in Software Engineering in Budapest University of Technology and Economics

View all posts by kindsonthegenius →

One thought on “Json Web Token – How to Secure REST API Using JWT in Spring Boot

Leave a Reply

Your email address will not be published. Required fields are marked *