Mock JWT Decoding with JwtDecoder in JUnit Tests
JSON web tokens (JWTs) are widely used for microservices and Spring security authentication. When testing Spring applications, mocking JWT decoding is often necessary. Let us delve into understanding how to test using Junit and mock JWT decoding with JwtDecoder.
1. Introduction to JWT and JwtDecoder
1.1 What is JWT?
JWT (JSON Web Token) is a compact, URL-safe token format used for securely transmitting information between parties. It is widely used for authentication and authorization in web applications. The JWT token follows the following structure:
1 2 3 4 5 | -- Structure of a JWT Token Header.Payload.Signature -- Sample of a JWT token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMSIsImV4cCI6MTY5MjM4Mjg4MH0.Df5ZyV8T1j1WQQ4u4xNhPvDTrMQ1rP_7M5kg5lPaP_U |
1.1.1 Understanding the JWT Token
In the JWT token, each part of the token is separated by a dot (.
):
- Header: Contains metadata about the token, including the signing algorithm and token type. It includes:
alg
: The signing algorithm used (e.g., HS256).typ
: The type of token (e.g., JWT).
1{"alg":"HS256","typ":"JWT"}
- Payload: Holds the claims, which are pieces of information about the user or token. It includes:
sub
: Subject (e.g., the user identifier).exp
: Expiration time (e.g., Unix timestamp).
1{"sub":"user1","exp":1692382880}
- Signature: Ensures the token’s integrity and authenticity using a secret key or public-private key pair. It is generated by:
- Encoding the header and payload.
- Signing the encoded data using a secret key or private key.
12345HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret-key
)
1.2 What is JwtDecoder?
JwtDecoder is an interface in Spring Security that decodes JWTs to extract claims.
1 2 3 | // Code Syntax JwtDecoder jwtDecoder = NimbusJwtDecoder.withSecretKey(secretKey).build(); Jwt jwt = jwtDecoder.decode(token); |
The code snippet demonstrates the syntax for decoding a JWT using JwtDecoder
in Spring Security. The first line initializes a JwtDecoder
instance using NimbusJwtDecoder
, which is configured with a secret key for HMAC-based JWT validation. The method withSecretKey(secretKey)
specifies the secret key used for verifying the JWT signature, and the build()
method finalizes the decoder’s configuration. In the second line, the decode(token)
method of JwtDecoder
is called to parse and validate the given JWT token. If the token is valid, it returns a Jwt
object containing the token’s claims (such as subject, expiration time, and custom attributes). If the token is invalid, expired, or tampered with, an exception is thrown. This syntax is commonly used in Spring Security’s OAuth2 resource server implementations to authenticate and authorize API requests.
1.2.1 How does JwtDecoder work?
The JwtDecoder
plays a crucial role in JWT-based authentication by verifying and extracting information from tokens. It ensures that the received JWT is valid, untampered, and correctly signed before allowing access to protected resources. Without proper decoding and validation, unauthorized users could manipulate tokens and gain access to sensitive data.
The verification process follows these steps:
- It verifies the JWT’s signature to ensure it hasn’t been tampered with.
- It decodes the token to extract claims such as subject, issuer, and expiration time.
- It can work with:
- A secret key (for HMAC-signed tokens like HS256).
- A public key (for RSA-signed tokens like RS256).
- A JWK Set URI (to dynamically fetch public keys from an authorization server).
1.2.2 Advantages of JwtDecoder
Key advantages of JwtDecoder
include:
- Provides secure JWT verification without manually handling cryptographic logic.
- Supports different types of JWT signing mechanisms (HMAC, RSA, EC).
- Integrates seamlessly with Spring Security’s OAuth2 Resource Server.
2. Code Example
2.1 Adding Dependencies
To use JWT in Spring Security, add dependencies in pom.xml
file:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <!-- Spring Security Starter for basic authentication and authorization --> < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-security</ artifactId > </ dependency > <!-- JJWT (Java JWT) library for creating and parsing JWT tokens --> < dependency > < groupId >io.jsonwebtoken</ groupId > < artifactId >jjwt</ artifactId > < version >0.11.5</ version > </ dependency > <!-- Spring Security OAuth2 Resource Server for handling JWT authentication --> < dependency > < groupId >org.springframework.security</ groupId > < artifactId >spring-security-oauth2-resource-server</ artifactId > </ dependency > <!-- JUnit Jupiter API for writing and running unit tests --> < dependency > < groupId >org.junit.jupiter</ groupId > < artifactId >junit-jupiter-api</ artifactId > < scope >test</ scope > </ dependency > |
2.2 Mocking JwtDecoder in Junit
We use Mockito to mock the JwtDecoder
behavior in JUnit tests.
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | import static org.mockito.Mockito.*; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtException; import java.time.Instant; public class JwtDecoderTest { @Test void testValidJwtDecoder() { // Mock JwtDecoder JwtDecoder jwtDecoder = mock(JwtDecoder. class ); // Create a mock valid JWT token Jwt validJwt = Jwt.withTokenValue( "valid-token" ) .header( "alg" , "HS256" ) .claim( "sub" , "test-user" ) .claim( "role" , "ADMIN" ) .issuedAt(Instant.now()) .expiresAt(Instant.now().plusSeconds( 3600 )) // Expires in 1 hour .build(); // Define behavior for valid token when(jwtDecoder.decode( "valid-token" )).thenReturn(validJwt); // Test JWT decoding Jwt decodedJwt = jwtDecoder.decode( "valid-token" ); // Assertions for valid JWT assertNotNull(decodedJwt); assertEquals( "test-user" , decodedJwt.getClaim( "sub" )); assertEquals( "ADMIN" , decodedJwt.getClaim( "role" )); } @Test void testExpiredJwtDecoder() { // Mock JwtDecoder JwtDecoder jwtDecoder = mock(JwtDecoder. class ); // Create a mock expired JWT token Jwt expiredJwt = Jwt.withTokenValue( "expired-token" ) .header( "alg" , "HS256" ) .claim( "sub" , "expired-user" ) .claim( "role" , "USER" ) .issuedAt(Instant.now().minusSeconds( 7200 )) // Issued 2 hours ago .expiresAt(Instant.now().minusSeconds( 3600 )) // Expired 1 hour ago .build(); // Define behavior for expired token when(jwtDecoder.decode( "expired-token" )).thenReturn(expiredJwt); // Test JWT decoding Jwt decodedJwt = jwtDecoder.decode( "expired-token" ); // Assertions for expired JWT (claims exist, but it should be considered expired) assertNotNull(decodedJwt); assertEquals( "expired-user" , decodedJwt.getClaim( "sub" )); assertEquals( "USER" , decodedJwt.getClaim( "role" )); // Additional check (if implementing real JWT validation, this should throw an exception) assertTrue(decodedJwt.getExpiresAt().isBefore(Instant.now()), "Token should be expired" ); } @Test void testInvalidJwtDecoder() { // Mock JwtDecoder JwtDecoder jwtDecoder = mock(JwtDecoder. class ); // Define behavior for invalid token (simulate decoding failure) when(jwtDecoder.decode( "invalid-token" )).thenThrow( new JwtException( "Invalid JWT token" )); // Test invalid JWT decoding Exception exception = assertThrows(JwtException. class , () -> jwtDecoder.decode( "invalid-token" )); // Assertion for invalid JWT exception message assertEquals( "Invalid JWT token" , exception.getMessage()); } } |
2.2.1 Code Explanation and Output
The JwtDecoderTest
class demonstrates unit testing of JWT decoding using a mocked JwtDecoder
in Java with JUnit. It includes three test cases to handle different JWT scenarios: valid, expired, and invalid tokens. In the first test, testValidJwtDecoder()
, a valid JWT token is created with claims such as sub
(subject) and role
(ADMIN), along with an expiration time set to one hour ahead. The mock JwtDecoder
is configured to return this token when decoding “valid-token”, and assertions verify that the correct claims are extracted. The second test, testExpiredJwtDecoder()
, generates a JWT that expired one hour ago, verifying that the claims still exist but the expiration check confirms its invalidity. The final test, testInvalidJwtDecoder()
, simulates an invalid JWT scenario by making the mock JwtDecoder
throw a JwtException
when attempting to decode “invalid-token”, ensuring that the application correctly handles decoding failures. This approach ensures robust JWT validation while leveraging Mockito for efficient unit testing.
Once the test runs are successfully executed, the output will be:
1 2 | BUILD SUCCESSFUL in 1s 3 tests passed |
If an assertion fails, JUnit will generate an error message like:
1 2 3 | org.opentest4j.AssertionFailedError: Expected :test-user Actual :wrong-user |
3. Conclusion
Mocking JWT decoding with JwtDecoder
is crucial for writing secure and efficient tests without relying on external authentication services. Using Mockito, we can simulate JWT validation, extract claims, and test authentication logic in Spring Security applications effectively.