DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Enterprise AI Trend Report: Gain insights on ethical AI, MLOps, generative AI, large language models, and much more.

2024 Cloud survey: Share your insights on microservices, containers, K8s, CI/CD, and DevOps (+ enter a $750 raffle!) for our Trend Reports.

PostgreSQL: Learn about the open-source RDBMS' advanced capabilities, core components, common commands and functions, and general DBA tasks.

AI Automation Essentials. Check out the latest Refcard on all things AI automation, including model training, data security, and more.

Related

  • DataWeave: Play With Dates (Part 1)
  • Tired of Messy Code? Master the Art of Writing Clean Codebases
  • The Long Road to Java Virtual Threads
  • Spring OAuth Server: Token Claim Customization

Trending

  • Integrating Salesforce APEX REST
  • An Explanation of Jenkins Architecture
  • Telemetry Pipelines Workshop: Introduction To Fluent Bit
  • Generative AI With Spring Boot and Spring AI
  1. DZone
  2. Data Engineering
  3. Data
  4. Delegating JWT Validation for Greater Flexibility

Delegating JWT Validation for Greater Flexibility

Java decoupled solution for validating JSON Web Tokens, using callbacks and thus promoting decoupling and flexibility.

By 
Horatiu Dan user avatar
Horatiu Dan
·
Jun. 09, 22 · Tutorial
Like (5)
Save
Tweet
Share
6.9K Views

Join the DZone community and get the full member experience.

Join For Free

In my opinion, the purpose of all software applications that have been created so far, are being and will be developed should primarily be to make humans' day-to-day activities easier to fulfill. Humans are the most valuable creations, and software applications are great tools that at least could be used by them.

Nowadays, almost every software product exchanges data with at least one other peer software product, which results in huge amounts of data flowing among them. Usually, a request from one product to another needs to pass a set of preconditions before it is considered acceptable and trustworthy.

The purpose of this article is to showcase a simple and flexible yet efficient and decoupled solution for validating such prerequisites.

Setting the Stage

Let's consider the next simple and general use case:

  • Service Provider and Client are two applications exchanging data.
  • The Client calls the Service Provider. 
  • The operation invoked is executed only after the Client is identified by the Service Provider. 
  • The Client identification is done via a token included in the request and validated by the Service Provider.

As part of this article, a small Java project is built, and while doing this, the token validation strategy is explained.

As JSON Web Tokens (JWT) are widely used nowadays, especially when products need to identify among others, JWT validation was chosen as the concrete implementation. According to RFC7519, a JWT is a compact, encoded, URL-safe string representation of a JSON message.

Very briefly, a JWT has three sections - header, payload, and signature.

Encoded, it is a string with three sections, separated by dots:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJoY2QiLCJpc3MiOiJpc3N1ZXIiLCJhdWQiOiJhdWRpZW5jZSIsImV4cCI6MTY1MDU0OTg1OH0.rbs6NqNw9KZ4IGuCOjdPpdJqMswTXHn7oNADCzlQHL8 

Decoded, it is in JSON format and thus, more readable:

Header - algorithm and type

JSON
 
{
  "alg": "HS256",
  "typ": "JWT"
}


Payload - data (claims)

JSON
 
{
  "sub": "hcd",
  "iss": "issuer",
  "aud": "audience",
  "exp": 1650549858
}


Signature

JavaScript
 
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  the-256-bit-secret
)


These pieces of information are enough to have an idea about JWTs; let's start developing.

Initial Implementation

The sample project is built with Java 17 and Maven. The dependencies are very few:

  • io.jsonwebtoken / jjwt - for JWT signing and verification
XML
 
<dependency>
	<groupId>io.jsonwebtoken</groupId>
	<artifactId>jjwt</artifactId>
	<version>0.9.1</version>
</dependency>


For exploring other available libraries, check https://jwt.io/libraries?language=Java.

  • JUnit 5 and Mockito - for unit testing, the implementation
XML
 
<dependency>
	<groupId>org.junit.jupiter</groupId>
	<artifactId>junit-jupiter-engine</artifactId>
	<version>5.8.2</version>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>org.mockito</groupId>
	<artifactId>mockito-junit-jupiter</artifactId>
	<version>4.5.1</version>
	<scope>test</scope>
</dependency>


JWT generation and verification is implemented using the following interface:

Java
 
public interface JwtManager {

    String generate(String sub, String iss, String aud);

    boolean isValid(String jwt, String iss, String aud);
}


The former method uses the provided parameters (subject, issuer, and audience) to create and sign a valid a JWT. The latter checks whether the jwt is valid or not, using the provided issuer and audience.

The goal is to create an implementation and make the following test pass.

Java
 
class JwtManagerTest {

    private String iss;
    private String aud;
    private String jwt;
    private JwtManager jwtManager;

    @BeforeEach
    void setUp() {
        jwtManager = new JwtManagerImpl();

        iss = "issuer";
        aud = "audience";
        jwt = jwtManager.generate("hcd", iss, aud);
        Assertions.assertNotNull(jwt);
    }

    @Test
    void isValid_coupled() {
        final boolean valid = jwtManager.isValid(jwt, iss, aud);
        Assertions.assertTrue(valid);
    }
}


By leveraging the Jwts builder, the sub, iss, and aud are set, the token is configured to expire after 1 minute, and moreover, it is signed using the Service Provider secret key.

Java
 
public String generate(String sub, String iss, String aud) {
	final Date exp = new Date(System.currentTimeMillis() + 60_000);

	return Jwts.builder()
		.setSubject(sub)
		.setIssuer(iss)
		.setAudience(aud)
		.setExpiration(exp)
		.signWith(SignatureAlgorithm.HS256, "s1e2c3r4e5t6k7e8y9")
		.compact();
}


In the other direction, the token is parsed using the same secret key, and if it hasn't expired yet, the payload claims are extracted.

Java
 
public boolean isValid(String jwt, String iss, String aud) {
	Claims body;
	try {
		body = Jwts.parser()
				.setSigningKey("s1e2c3r4e5t6k7e8y9")
				.parseClaimsJws(jwt)
				.getBody();
	} catch (JwtException e) {
		return false;
	}

	return iss.equals(body.getIssuer()) &&
			aud.equals(body.getAudience());
}


This is straightforward. Nevertheless, a custom assumption is made in addition to the standard (mandatory) token validations.

"A valid token is acceptable if the issuer and audience conform to specific values."

Basically, this is the plot of the article - how to implement the custom verification for a valid token, as flexible as possible.

If we run the test, it passes, and the implementation is correct, but unfortunately, not flexible enough.

At some point, the Service Provider that validates the Client's request changes the assumption that has been previously made. This obviously impacts isValid() method, whose implementation should to be changed.

Final Implementation

It would be good if whenever the Service Provider makes a change to these preconditions, the standard part of the token validation remains in place. Then the code shall be flexible enough to allow deciding on the custom validation assumptions as late as possible. In order to accommodate this, the code needs to be refactored.

What's been stated it's enclosed in the next interface (even better, @FunctionalInterface).

Java
 
@FunctionalInterface
public interface ValidationStrategy {

    boolean isValid(Claims body);
}


The strategy is implemented, and the last two lines in the isValid() method are moved in the newly implemented strategy. Moreover, we may assume that this is the default validation strategy of the Service Provider.

Java
 
public class DefaultValidationStrategy implements ValidationStrategy {

    private final String iss;
    private final String aud;

    public DefaultValidationStrategy(String iss, String aud) {
        this.iss = iss;
        this.aud = aud;
    }

    @Override
    public boolean isValid(Claims body) {
        return iss.equals(body.getIssuer()) &&
                aud.equals(body.getAudience());
    }
}


The former method is first deprecated and soon replaced by the new implementation below.

Java
 
public interface JwtManager {

    String generate(String sub, String iss, String aud);

    /**
     * @deprecated in favor of {@link #isValid(String, ValidationStrategy)}
     */
    @Deprecated(forRemoval = true)
    boolean isValid(String jwt, String iss, String aud);

    boolean isValid(String jwt, ValidationStrategy strategy);
}


Basically, the new method delegates to the ValidationStrategy callback. Delegation (in programming) means exactly this; one entity passes something to another entity.

Java
 
public boolean isValid(String jwt, ValidationStrategy strategy) {
	Claims body;
	try {
		body = Jwts.parser()
				.setSigningKey(SECRET)
				.parseClaimsJws(jwt)
				.getBody();
	} catch (JwtException e) {
		return false;
	}

	return strategy.isValid(body);
}


In use, the validation is performed as in the following unit test.

Java
 
@Test
void isValid_looselyCoupled_defaultStrategy() {
	final boolean valid = jwtManager.isValid(jwt,
			new DefaultValidationStrategy(iss, aud));
	Assertions.assertTrue(valid);
} 


With these modifications, the code is flexible enough to accommodate potential changes in the validation strategy. For instance, if the Service Provider decides to check only the issuer, this can be achieved without needing to modify the code that handles the JWT standard part.

Java
 
@Test
void isValid_looselyCoupled_customStrategy() {
	final boolean valid = jwtManager.isValid(jwt,
			body -> iss.equals(body.getIssuer()));
	Assertions.assertTrue(valid);
}


If we have a look at the previous unit test, we see how handful it is to pass the ValidationStrategy using lambda. Also, I suppose it's clear the reason for making the ValidationStrategy a @FunctionalInterface from the beginning.

In this article, a decoupled solution for validating JSON Web Tokens was implemented. This solution uses callbacks and thus promotes decoupling and flexibility.

JWT (JSON Web Token) Strings Data Types

Published at DZone with permission of Horatiu Dan. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • DataWeave: Play With Dates (Part 1)
  • Tired of Messy Code? Master the Art of Writing Clean Codebases
  • The Long Road to Java Virtual Threads
  • Spring OAuth Server: Token Claim Customization

Partner Resources


Comments

ABOUT US

  • About DZone
  • Send feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends: