How to Secure REST APIs with Open Liberty and Jakarta EE: JWT Authentication Tutorial

This tutorial guides you through building a secure REST API using Jakarta EE, Java 21, Open Liberty, and JWT (JSON Web Tokens) for authentication. We’ll use CDI (Contexts and Dependency Injection) for dependency management, a Java record for the data model, and JUnit 5 with JerseyTest (using Grizzly to emulate GlassFish) for integration testing. Additionally, we’ll add Mockito unit tests for the JwtGenerator and TaskResource classes to achieve 100% code coverage. Each code block includes detailed descriptions and inline comments, and diagrams illustrate the architecture. Links to external resources are provided for beginners.


Prerequisites


Project Overview

  • Backend: A Jakarta EE application with a REST API for task management, secured with JWT authentication, running on Open Liberty.
  • Authentication: Users log in to obtain a JWT, which is required to access protected endpoints.
  • Features:
    • Login endpoint to generate JWTs
    • Protected endpoint to list tasks
    • JWT validation for secure access
  • Technologies:
    • Java 21: For records and modern Java features.
    • Jakarta EE: For JAX-RS, CDI, and JSON processing.
    • Open Liberty: Application server for runtime.
    • CDI: For dependency injection.
    • Java Record: For the Task data model.
    • JUnit 5 with JerseyTest and Grizzly: For integration testing.
    • Mockito: For unit testing JwtGenerator and TaskResource with 100% coverage.

Architecture Diagram

Below is the architecture diagram for runtime and testing:

Explanation:

  • Runtime: The Client sends HTTP requests with a JWT to the Open Liberty server, which hosts the Jakarta EE app with JAX-RS endpoints, JWT authentication, CDI beans, and a Task record for data.
  • Integration Test: JUnit 5 with JerseyTest uses Grizzly (emulating GlassFish’s JAX-RS runtime) to test the API endpoints and JWT validation.
  • Unit Test: JUnit 5 with Mockito tests the JwtGenerator and TaskResource classes in isolation, mocking dependencies to achieve 100% code coverage. For more on testing strategies, see Baeldung’s Testing Guide.

Step 1: Set Up the Jakarta EE Project

1.1 Create the Maven Project

Create a Maven project with the following pom.xml, including dependencies for JerseyTest, Grizzly, Mockito, and Open Liberty:

xml

Description: The pom.xml:

  • Includes Jakarta EE, MicroProfile JWT, and JJWT for runtime.
  • Adds JUnit 5, Mockito, JerseyTest (with Grizzly), and Weld SE for testing.
  • Configures Open Liberty for runtime via the liberty-maven-plugin.
  • Enables 100% unit test coverage with Mockito for JwtGenerator and TaskResource. For more on Maven, see Maven in 5 Minutes.

1.2 Configure Open Liberty

Create src/main/liberty/config/server.xml to configure the Open Liberty server:

xml

Description: This file:

  • Enables Jakarta EE 10 for JAX-RS, CDI, and JSON-P.
  • Enables MicroProfile JWT for authentication.
  • Sets the server to listen on port 8080.
  • Configures JWT validation with issuer (example.com) and audience (task-api).
  • Enables CDI and deploys the app at /api. For more, see Open Liberty Configuration.

Step 2: Implement the Backend

2.1 Create the Task Record

Create com.example.model.Task as a Java record:

java

Description: This record:

  • Defines an immutable Task with id, title, and description components.
  • Automatically provides getters (id(), title(), description()), equals(), hashCode(), and toString().
  • Reduces boilerplate compared to a POJO, ideal for data transfer. For more, see Java Records Guide.

2.2 Create the Task Service

Create com.example.service.TaskService to manage tasks:

java

Description: This CDI bean:

  • Uses @ApplicationScoped for a single instance.
  • Stores tasks in an in-memory List (simulating a database).
  • Initializes sample tasks with the Task record.
  • Returns a defensive copy of tasks to maintain immutability. For more on CDI, see CDI Guide.

2.3 Create the JWT Generator

Create com.example.auth.JwtGenerator to generate JWTs:

java

Description: This CDI bean:

  • Generates JWTs using the JJWT library.
  • Uses a hardcoded secret (in production, use environment variables or a vault).
  • Sets issuer, audience, and a 1-hour expiration, matching server.xml. For more on JWTs, see JWT Introduction.

2.4 Create the REST API

Create com.example.rest.TaskResource for the REST endpoints:

java

Description: This JAX-RS resource:

  • Defines /tasks with JSON input/output.
  • Provides /login to generate a JWT and /tasks to list tasks, protected with @RolesAllowed(“user”).
  • Uses CDI to inject TaskService and JwtGenerator.
  • The Task record ensures compatibility with JSON serialization. For more on JAX-RS, see JAX-RS Guide.

2.5 Configure JAX-RS Application

Create com.example.rest.JaxRsActivator to activate JAX-RS:

java

Description: This sets the JAX-RS base path to /api, making endpoints accessible at /api/tasks. For more, see JAX-RS Application Setup.


Step 3: Test the Application

3.1 Unit Tests with Mockito

To achieve 100% code coverage for JwtGenerator and TaskResource, we’ll write Mockito unit tests that mock dependencies and test all methods and branches.

3.1.1 Unit Test for JwtGenerator

Create src/test/java/com.example.auth.JwtGeneratorTest:

java

Description: This test:

  • Tests the generateJwt method, covering 100% of JwtGenerator’s code.
  • Verifies the JWT’s subject, issuer, audience, issuance time, and expiration.
  • Uses JJWT to parse the JWT and check claims.
  • Ensures the secret key matches JwtGenerator’s hardcoded key. For more on Mockito, see Mockito Guide.

3.1.2 Unit Test for TaskResource

Create src/test/java/com.example.rest.TaskResourceTest:

java

Description: This test:

  • Achieves 100% coverage for TaskResource by testing login and getAllTasks.
  • Mocks TaskService and JwtGenerator using @Mock.
  • Injects mocks into TaskResource with @InjectMocks.
  • Verifies HTTP status, response data, and mock interactions. Note: The @RolesAllowed(“user”) annotation is not tested here, as it’s enforced by the container (Open Liberty). Integration tests with JerseyTest cover this. For more on Mockito, see Baeldung’s Mockito Tutorial.

3.2 Integration Tests with JerseyTest

Create src/test/java/com.example.rest.TaskResourceIntegrationTest for integration tests using JerseyTest with Grizzly:

java

Description: This integration test:

  • Uses JerseyTest with Grizzly to simulate the JAX-RS runtime.
  • Tests the /login endpoint, the /tasks endpoint with a valid JWT, and the /tasks endpoint without a JWT.
  • Simplifies JWT validation due to Grizzly’s lack of full MicroProfile JWT support (in production, use Open Liberty for integration tests). For more, see Jersey Test Framework.

3.3 Run the Application and Tests

  1. Start Open Liberty (for runtime):

bash

The API is available at http://localhost:8080/api.

  1. Test the API using Postman or curl:
    • Login:bashcurl -X POST "http://localhost:8080/api/tasks/login?username=testuser"Response: {“jwt”:”eyJhbG…”}
    • Get Tasks (with JWT):bashcurl -H "Authorization: Bearer <your-jwt>" http://localhost:8080/api/tasksResponse: [{“id”:1,”title”:”Task 1″,”description”:”First task description”}, …]
    • Get Tasks (without JWT):bashcurl http://localhost:8080/api/tasksResponse: 401 Unauthorized
  2. Run Tests:

bash

This runs both unit tests (Mockito) and integration tests (JerseyTest), achieving 100% coverage for JwtGenerator and TaskResource.

Why? The liberty:run command starts Open Liberty, and mvn test executes all tests. For more, see Open Liberty Testing Guide.


Step 4: Verify Code Coverage

To confirm 100% coverage for JwtGenerator and TaskResource:

  • Use a tool like JaCoCo by adding the JaCoCo Maven plugin to pom.xml:

xml

  • Run tests with coverage:bashmvn test
  • Check the coverage report in target/site/jacoco/index.html.

The Mockito tests cover all lines and branches in JwtGenerator and TaskResource, ensuring 100% coverage for these classes.


Step 5: Deploying the Application (Optional)

To deploy:

  • Package the app:bashmvn clean package
  • Deploy target/jwt-secure-api.war to an Open Liberty server in production. See Open Liberty Deployment Guide.

Security Considerations

  • Secure Key Management: Store the JWT signing key in environment variables or a vault.
  • HTTPS: Enable HTTPS in server.xml for production.
  • JWT Validation in Tests: The integration tests simplify JWT validation. For production, use Open Liberty or a mock JWT authenticator in JerseyTest.
  • Role Mapping: Configure role mappings in server.xml for granular access.

For more, see Open Liberty Security Guide.


Conclusion

You’ve built a secure REST API using Jakarta EE, Java 21, Open Liberty, and JWT authentication, with:

  • A Java record for the Task model, ensuring immutability.
  • CDI for dependency injection.
  • JerseyTest with Grizzly for integration testing.
  • Mockito unit tests for JwtGenerator and TaskResource, achieving 100% code coverage.
  • MicroProfile JWT for authentication.

The /login endpoint generates JWTs, and the /tasks endpoint requires a valid JWT. Detailed comments, diagrams, and comprehensive tests ensure clarity and reliability.

Next Steps

For further learning:

Leave a Comment

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

Scroll to Top