JWT Authentication With Spring Boot’s Inbuilt OAuth2 Resource Server
By: Imesha Sudasingha
Tags: Jwt, Spring Boot, Rest Api, Spring Security, Authentication

The REST API Implementation

Setting up the project

Start a new maven (or gradle; I’m using maven in this article) project with the following dependencies in it:

Spring Web

OAuth2 Resource Server

Lombok (optional) — A dev tool to use annotations instead of writing getters and equals methods.

You can start a spring boot project in spring initializr with the above dependencies or add the following dependencies to your project:

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>

<version>2.3.4.RELEASE</version>

</dependency> <dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-web</artifactId>

<version>2.3.4.RELEASE</version>

</dependency>

Generate a java keystore and a keypair for JWT signing and validation

In order to sign JWTs, we need an RSA Private Key and to validate a JWT, we need the RSA Public Key corresponding to that private key. To store the keypair, we need a key store.

Use the following java keytool (shipped with the JDK) command to generate a key pair.

keytool -genkey -alias <alias> -keyalg RSA -keystore <keystore_name> -keysize 2048

alias — A name to uniquely identify the generated keypair entry within the generate keystore. Let’s use “jwtsigning”

— A name to uniquely identify the generated keypair entry within the generate keystore. Let’s use “jwtsigning” keyalg — Public key algorithm. Should be “RSA” for our case.

— Public key algorithm. Should be “RSA” for our case. keystore — A name for the keystore file generated. Let’s use “keystore.jks”

— A name for the keystore file generated. Let’s use “keystore.jks” keysize — Size (a measure of strength) of the generated public key. We should set that to 2048 at least. Can be set to 4096 for better security (to further reduce the possibility of an attacker guessing your keys).

I’m using the following command to generate the keypair and move on with our solution:

keytool -genkey -alias jwtsigning -keyalg RSA -keystore keystore.jks -keysize 2048

This command will ask for a password for the keystore and a password for the generated private key entry. Let’s use the word “password” for both passwords. Make sure to use a strong password for production scenarios. This command will ask few other questions as well, answer them accordingly.

Now, you will see a keystore.jks file created in your current directory. Copy that to the following directory.

src/main/resources/keys/keystore.jks

Configuring HttpSecurity

Next we have to configure HttpSecurity by creating a new bean extending the WebSecurityConfigurerAdapter as follows:

@Component

public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override

protected void configure(HttpSecurity http) throws Exception {

http

.cors()

.and()

.csrf().disable()

.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)

.and()

.authorizeRequests(configurer ->

configurer

.antMatchers(

"/error",

"/login"

)

.permitAll()

.anyRequest()

.authenticated()

)

.exceptionHandling().disable()

.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);

}

}

Here we have done the following configurations:

cors() — Adds a CORS filter

csrf().disable() — Disable CSRF protection as we won’t be needing that.

sessionManagement()… — We set the session management policy to STATELESS because we are not going to use sessions (state) in this case.

authorizeRequests(…) — Allow “ /login ” endpoint to be accessed without authentication and marks all the requests to other endpoints to as authentication required.

” endpoint to be accessed without authentication and marks all the requests to other endpoints to as authentication required. exceptionHandling().disable() — Disable exception handling of spring MVC (which returns an error page) as we are developing a REST API.

oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt) — Configures the spring boot application as an OAuth2 Resource Server which authenticates all the incoming requests (except the ones excluded above) using JWT authentication. That is, all the requests should have an authorization header as below which contains a valid JWT.

Authorization: Bearer <jwt_token>

The addition of OAuth2 Resource Server with JWT configuration adds the following components mainly:

BearerTokenAuthenticationFilter — This is the filter responsible for authenticating the incoming requests with JWTs in header.

— This is the filter responsible for authenticating the incoming requests with JWTs in header. JwtAuthenticationProvider — The BearerTokenAuthenticationFilter (above) delegates the actual authentication of JWTs to this class via the AuthenticationManager.

See Spring Security Architecture to learn more about how authentication and authorization are handled in spring security.

JwtDecoder — The component used by the JwtAuthenticationProvider (above) to decode and validate received JWTs. This validation includes signature validation and JWT expiry validations. We will be using a custom decoder instead of the auto configured JwtDecoder to implement our solution.

Create the JWT Decoder

As mentioned above, JwtDecoder is used to decode the incoming JWTs. Right now, the auto-configured JwtDecoder can take the RSA public key (JWT validation key) as a string, from “jwk-set-uri” or indirectly via “jwt issuer uri”.

See the spring boot OAuth2ResourceServerJwtConfiguration class and Spring Security OAuth2 Authorization Server documentation to understand already available JWT decoder creation methods.

But, for our case, we need to read the public key from the key store. To achieve that, we write the following configuration class to ultimately create the desired JwtDecoder.

JwtConfiguration.java

This class requires following “application.properties” entries to load the keys from keystore.

app.security.jwt.keystore-location=keys/keystore.jks

app.security.jwt.keystore-password=password

app.security.jwt.key-alias=jwtsigning

app.security.jwt.private-key-passphrase=password

These values specify the keystore location (“src/main/resources/keys/keystore.jks”), keystore password, JWT signing key alias (see key pair generation step) and private key passphrase (=”password” in this case).

In the runtime, the above configuration class will load the keystore, jwt signing key (private key) and jwt validation key. Ultimately, it will return a JwtDecoder bean which uses the public key loaded from the keystore to validate JWTs.

@Bean

public JwtDecoder jwtDecoder(RSAPublicKey rsaPublicKey) {

return NimbusJwtDecoder.withPublicKey(rsaPublicKey).build();

}

which will be used by the JwtAuthenticationProvider bean to decode and validate the JWT.

Generating JWTs

So far we implemented JWT decoding and validation part. Now we need to implement the JWT generating part. For that we are going to use the JWT library by auth0. Add the following dependency to your maven project for that:

<dependency>

<groupId>com.auth0</groupId>

<artifactId>java-jwt</artifactId>

<version>3.11.0</version>

</dependency>

We need a helper class to create a signed JWT given the claims:

The RSA Public Key and the RSA Private Key will be created and autowired from the JwtConfiguration we created before.

/login endpoint (public)

Now we can write our “/login” endpoint which takes the user credentials and returns a JWT if credentials match. For that, we need some users in our system and a mechanism to verify user credentials.

Password Encoder

In order to encode user passwords, we need a PasswordEncoder. We will be using the BCryptPasswordEncoder.

To create the password encoder bean, we will be adding a new class named SecurityConfiguration annotated with “@Configuration” as follows:

@Configuration

public class SecurityConfiguration {



@Bean

public PasswordEncoder passwordEncoder() {

return new BCryptPasswordEncoder();

}

}

User Details Service

Spring has the UserDetailsService interface which is used to retrieve user data. Wegoing to use the InMemoryUserDetailsManager as the implementation of that interface. To define the UserDetailsService, we should override the userDetailsService() method from WebSecurityConfigurerAdapter within our WebSecurityConfig class created above. The overridden sections looks like below:

@Bean

@Override

protected UserDetailsService userDetailsService() {

UserDetails user1 = User

.withUsername("user")

.authorities("USER")

.passwordEncoder(passwordEncoder::encode)

.password("1234")

.build();



InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();

manager.createUser(user1);

return manager;

}

We have added a user with username “user” and password “password” to the returned InMemoryUserDetailsManager. The passwordEncoder here will be autowired.

Now our WebSecurityConfig looks like below:

Auth Controller (with “/login” endpoint)

Now we can simply write our “/login” endpoint within a controller. Let’s name it AuthController. It should look like below:

It contains the “/login” endpoint which consumes data type “application/x-www-form-urlencoded” as a POST request. It then checks if there’s a user is present. If yes, matches the provided password with the user’s password. If password matches, we use the JwtHelper class we created to generate the JWT.

Just to return the response we can use a resource class which contains only the jwt.

/**

* Login response object containing the JWT

*

* @author imesha

*/

@Data

@RequiredArgsConstructor

public class LoginResult {



@NonNull

private String jwt;

}

GET /user endpoint (protected)

Now let’s write a protected endpoint to fetch user details. The controller will be named as “UserController” and will look like below.

@RestController

@RequestMapping("/user")

public class UserController {



private final UserDetailsService userDetailsService;



public UserController(UserDetailsService userDetailsService) {

this.userDetailsService = userDetailsService;

}



@GetMapping

public UserDetails getUser(Authentication authentication) {

JwtAuthenticationToken token = (JwtAuthenticationToken) authentication;

Map<String, Object> attributes = token.getTokenAttributes();

return userDetailsService.loadUserByUsername(attributes.get("username").toString());

}

}

You may have noticed the Authentication parameter passed in the getUser() method. That is an instance of JwtAuthenticationToken which is populated by the in built BearerTokenAuthenticationFilter and JwtAuthenticationProvider we discussed earlier. It is passed automatically by Spring Boot as the request has been authenticated using the JWT.

This JwtAuthenticationToken contains the claims we set when creating the JWT as attributes. We can get the “username” claim from those attributes to load the user from the UserDetailsService.