This article shows how to build, containerize, and test a secure Jakarta EE 10 REST API using Open Liberty as both OpenID Connect (OIDC) provider and client. All container operations (build, run, stop, remove) are automated with Maven plugins in the integration-test module. You’ll use Java 21, CDI constructor injection, JPA, Flyway, Podman, and comprehensive unit/integration tests. All code and configuration are included and ready to use.
What is OIDC? Why Use It?
OpenID Connect (OIDC) is a modern authentication protocol built on top of OAuth 2.0, standardized in 2014 by the OpenID Foundation. It provides a simple, secure, and interoperable way for applications to authenticate users via a trusted identity provider. OIDC adds an identity layer to OAuth 2.0, introducing the concept of an ID token (a signed JWT) that securely conveys user identity.
OIDC vs. Other Protocols
Protocol | Purpose | Strengths | Limitations |
---|---|---|---|
OpenID Connect | AuthN + AuthZ | Simple, JWT-based, RESTful, SSO, supported by all major IdPs | Requires HTTPS, newer |
OAuth 2.0 | Authorization | Widely adopted, flexible | No authentication, no ID tokens |
SAML 2.0 | AuthN + AuthZ | Mature, enterprise SSO | XML-based, complex, not RESTful |
OpenID 2.0 | Authentication | Early SSO | Obsolete, replaced by OIDC |
OIDC Benefits:
- RESTful and developer-friendly (JSON, JWT)
- Secure (uses proven cryptography, JWT, HTTPS)
- Interoperable (works with Google, Microsoft, etc.)
- Enables SSO and delegated login
- Simpler than SAML, more modern than OpenID 2.0
- Overview
- Project Structure
- Step 1: Create the Project in IntelliJ
- Step 2: Parent and Child POMs
- Step 3: Production Code
- Step 4: Flyway Migration Script
- Step 5: persistence.xml & beans.xml
- Step 6: Open Liberty OIDC Provider & Client Config
- Step 7: Generate Self-Signed Certificate
- Step 8: Containerfile
- Step 9: Integration Test Module: Maven Plugins for Podman Lifecycle
- Step 10: Comprehensive Unit and Integration Tests
- Step 11: Build and Run Commands
- Troubleshooting
- References
Project Structure

Step 1: Create the Project in IntelliJ
Description:
Use IntelliJ’s “New Project” wizard to create a Maven parent project, then add two modules:
restapi-war
(packaging: war)integration-test
(packaging: jar)
Create the directory structure as above.
Step 2: Parent and Child POMs
restapi/pom.xml
(parent)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>restapi</artifactId> <version>1.0-SNAPSHOT</version> <packaging>pom</packaging> <modules> <module>restapi-war</module> <module>integration-test</module> </modules> <properties> <maven.compiler.source>21</maven.compiler.source> <maven.compiler.target>21</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <failIfNoTests>true</failIfNoTests> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>jakarta.platform</groupId> <artifactId>jakarta.jakartaee-api</artifactId> <version>10.0.0</version> <scope>provided</scope> </dependency> </dependencies> </dependencyManagement> </project> |
restapi-war/pom.xml
1 2 3 4 5 6 7 8 9 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 |
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <groupId>com.example</groupId> <artifactId>restapi</artifactId> <version>1.0-SNAPSHOT</version> </parent> <artifactId>restapi-war</artifactId> <packaging>war</packaging> <dependencies> <dependency> <groupId>jakarta.platform</groupId> <artifactId>jakarta.jakartaee-api</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.flywaydb</groupId> <artifactId>flyway-core</artifactId> <version>10.0.0</version> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.7.1</version> </dependency> <!-- Unit testing dependencies --> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>5.2.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.10.0</version> <scope>test</scope> </dependency> <dependency> <groupId>com.opentable.components</groupId> <artifactId>otj-pg-embedded</artifactId> <version>1.0.0</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>3.4.0</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.2.5</version> </plugin> </plugins> </build> </project> |
Step 3: Production Code
Person.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
package com.example.entity; import jakarta.persistence.*; @Entity public class Person { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; public Person() {} public Person(Long id, String name) { this.id = id; this.name = name; } public Long getId() { return id; } public String getName() { return name; } } |
PersonRepository.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
package com.example.repository; import com.example.entity.Person; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import jakarta.persistence.EntityManager; import java.util.List; @ApplicationScoped public class PersonRepository { private final EntityManager em; @Inject public PersonRepository(EntityManager em) { this.em = em; } public List<Person> findAll() { return em.createQuery("SELECT p FROM Person p", Person.class).getResultList(); } public Person save(Person person) { em.persist(person); return person; } } |
PersonResource.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
package com.example.resource; import com.example.entity.Person; import com.example.repository.PersonRepository; import jakarta.annotation.security.RolesAllowed; import jakarta.enterprise.context.RequestScoped; import jakarta.inject.Inject; import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; import java.util.List; @Path("/persons") @Produces(MediaType.APPLICATION_JSON) @RequestScoped public class PersonResource { private final PersonRepository repository; @Inject public PersonResource(PersonRepository repository) { this.repository = repository; } @GET @RolesAllowed("user") public List<Person> getAll() { return repository.findAll(); } } |
RestApiApplication.java
1 2 3 4 5 6 7 8 |
package com.example; import jakarta.ws.rs.ApplicationPath; import jakarta.ws.rs.core.Application; @ApplicationPath("/api") public class RestApiApplication extends Application {} |
Step 4: Flyway Migration Script
restapi-war/src/main/resources/db/migration/V1__init.sql
:
1 2 3 4 5 6 7 |
CREATE TABLE person ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL ); INSERT INTO person (name) VALUES ('Alice'), ('Bob'); |
Step 5: persistence.xml & beans.xml
restapi-war/src/main/resources/META-INF/persistence.xml
:
1 2 3 4 5 6 7 8 9 10 11 12 |
<persistence xmlns="https://jakarta.ee/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd" version="3.0"> <persistence-unit name="defaultPU" transaction-type="JTA"> <jta-data-source>jdbc/postgres</jta-data-source> <properties> <property name="jakarta.persistence.schema-generation.database.action" value="none"/> </properties> </persistence-unit> </persistence> |
beans.xml
1 2 3 4 5 |
<beans xmlns="https://jakarta.ee/xml/ns/jakartaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/beans_4_0.xsd" version="4.0"/> |
Step 6: Open Liberty OIDC Provider & Client Config
Open Liberty can act as both an OIDC Provider (issuing tokens) and OIDC Client (verifying tokens and authenticating users), making it ideal for end-to-end, standards-based authentication in enterprise Java.
restapi-war/src/main/liberty/config/server.xml
1 2 3 4 5 6 7 8 9 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 |
<server description="Liberty as OIDC Provider and Client"> <featureManager> <feature>jakartaee-10.0</feature> <feature>openidConnectServer-1.0</feature> <feature>openidConnectClient-1.0</feature> <feature>transportSecurity-1.0</feature> </featureManager> <variable name="default.http.port" value="9080"/> <variable name="default.https.port" value="9443"/> <httpEndpoint id="defaultHttpEndpoint" host="*" httpPort="${default.http.port}" httpsPort="${default.https.port}" /> <dataSource id="PostgresDS" jndiName="jdbc/postgres"> <jdbcDriver libraryRef="PostgresLib"/> <properties serverName="localhost" portNumber="5432" databaseName="postgres" user="postgres" password="postgres"/> </dataSource> <library id="PostgresLib"> <fileset dir="/config/resources" includes="postgresql-*.jar"/> </library> <basicRegistry id="basic" realm="oidcRealm"> <user name="testuser" password="testpass"/> </basicRegistry> <oauthProvider id="LocalOAuthProvider" userRegistryRef="basic"> <localStore> <client name="restapi-client" secret="test-secret" displayname="restapi-client" scope="openid profile email"> <redirect>https://localhost:9443/oidcclient/redirect/restapi-client</redirect> </client> </localStore> </oauthProvider> <openidConnectProvider id="LocalOIDCProvider" oauthProviderRef="LocalOAuthProvider" /> <openidConnectClient id="restapi-client" clientId="restapi-client" clientSecret="test-secret" authorizationEndpointUrl="https://localhost:9443/oidc/endpoint/LocalOIDCProvider/authorize" tokenEndpointUrl="https://localhost:9443/oidc/endpoint/LocalOIDCProvider/token" userInfoEndpointUrl="https://localhost:9443/oidc/endpoint/LocalOIDCProvider/userinfo" jwkEndpointUrl="https://localhost:9443/oidc/endpoint/LocalOIDCProvider/jwk" issuerIdentifier="https://localhost:9443/oidc/endpoint/LocalOIDCProvider" signatureAlgorithm="RS256" scope="openid profile email" redirectToRPHostAndPort="true" /> <keyStore id="defaultKeyStore" password="changeit" location="${server.config.dir}/resources/security/BasicKeyStore.p12" type="PKCS12"/> </server> |
Step 7: Generate Self-Signed Certificate
run the below steps to setup a self signed certificate in a keystore
1 2 3 4 5 6 7 8 9 10 11 12 |
mkdir -p restapi-war/src/main/liberty/config/resources/security keytool -genkeypair \ -alias liberty \ -keyalg RSA \ -keysize 2048 \ -validity 365 \ -keystore restapi-war/src/main/liberty/config/resources/security/BasicKeyStore.p12 \ -storetype PKCS12 \ -storepass changeit \ -dname "CN=localhost, OU=Dev, O=Example, L=City, S=State, C=US" |
Step 8: Containerfile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
FROM icr.io/appcafe/open-liberty:kernel-slim-java21-openj9-ubi ARG VERSION=1.0 ARG REVISION=SNAPSHOT LABEL \ org.opencontainers.image.authors="Your Name" \ org.opencontainers.image.vendor="Open Liberty" \ org.opencontainers.image.url="local" \ org.opencontainers.image.version="$VERSION" \ org.opencontainers.image.revision="$REVISION" \ name="restapi" \ version="$VERSION-$REVISION" COPY --chown=1001:0 target/liberty/config /config/ COPY --chown=1001:0 target/*.war /config/apps # Download and setup open liberty features as defined in server.xml RUN features.sh # Make the applocation server port accesible from outside the container EXPOSE 9080 |
Step 9: Integration Test Module: Maven Plugins for Podman Lifecycle
integration-test/pom.xml
1 2 3 4 5 6 7 8 9 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 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.example</groupId> <artifactId>restapi</artifactId> <version>1.0-SNAPSHOT</version> </parent> <artifactId>integration-test</artifactId> <dependencies> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.10.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.httpcomponents.client5</groupId> <artifactId>httpclient5</artifactId> <version>5.2.1</version> <scope>test</scope> </dependency> </dependencies> <build> <!-- Copy WAR file --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>3.6.0</version> <executions> <execution> <id>copy-war</id> <phase>process-resources</phase> <goals> <goal>copy</goal> </goals> <configuration> <artifactItems> <artifactItem> <groupId>com.example</groupId> <artifactId>${project.parent.artifactId}-war</artifactId> <version>${project.version}</version> <type>war</type> <outputDirectory>${project.build.directory}</outputDirectory> <destFileName>${project.parent.artifactId}-war.war</destFileName> </artifactItem> </artifactItems> </configuration> </execution> </executions> </plugin> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>3.3.1</version> <executions> <execution> <id>copy-liberty-config</id> <phase>process-resources</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <outputDirectory>${project.build.directory}/liberty-config</outputDirectory> <resources> <resource> <directory>${project.parent.basedir}/${project.parent.artifactId}-war/src/main/liberty/config</directory> <filtering>false</filtering> </resource> </resources> </configuration> </execution> </executions> </plugin> <plugins> <!-- Build the image with Podman --> <plugin> <groupId>io.github.lexemmens</groupId> <artifactId>podman-maven-plugin</artifactId> <version>0.4.1</version> <executions> <execution> <id>build-image</id> <phase>pre-integration-test</phase> <goals> <goal>build</goal> </goals> <configuration> <containerfile>${project.parent.basedir}/Containerfile</containerfile> <tags> <tag>restapi:latest</tag> </tags> <contextDirectory>${project.parent.basedir}</contextDirectory> </configuration> </execution> </executions> </plugin> <!-- Start the containers --> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>3.1.0</version> <executions> <execution> <id>start-podman-containers</id> <phase>pre-integration-test</phase> <goals> <goal>exec</goal> </goals> <configuration> <executable>sh</executable> <arguments> <argument>-c</argument> <argument> <![CDATA[ podman run -d --name pgtest -e POSTGRES_PASSWORD=postgres -p 5432:5432 postgres:16 && \ podman run -d --name libertytest --link pgtest:db -p 9080:9080 -p 9443:9443 restapi:latest && \ sleep 20 ]]> </argument> </arguments> </configuration> </execution> <execution> <id>stop-podman-containers</id> <phase>post-integration-test</phase> <goals> <goal>exec</goal> </goals> <configuration> <executable>sh</executable> <arguments> <argument>-c</argument> <argument> <![CDATA[ podman stop libertytest pgtest || true && \ podman rm libertytest pgtest || true && \ podman system prune -f ]]> </argument> </arguments> </configuration> </execution> </executions> </plugin> <!-- Run integration tests --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> <version>3.2.5</version> <configuration> <includes> <include>**/*IT.java</include> </includes> </configuration> <executions> <execution> <goals> <goal>integration-test</goal> <goal>verify</goal> </goals> </execution> </executions> </plugin> </plugins> <profiles> <profile> <id>integration-test</id> <activation> <activeByDefault>false</activeByDefault> </activation> </profile> </profiles> </build> </project> |
Step 10: Comprehensive Unit and Integration Tests
Unit Tests
PersonRepositoryTest.java
1 2 3 4 5 6 7 8 9 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 |
package com.example.repository; import com.example.entity.Person; import jakarta.persistence.EntityManager; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.*; import java.util.Arrays; import java.util.List; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; class PersonRepositoryTest { @Mock EntityManager em; @InjectMocks PersonRepository repo; @BeforeEach void setUp() { MockitoAnnotations.openMocks(this); } @Test void testFindAllReturnsList() { List<Person> expected = Arrays.asList(new Person(1L, "Alice"), new Person(2L, "Bob")); when(em.createQuery(anyString(), eq(Person.class))) .thenReturn(new TypedQueryStub<>(expected)); List<Person> result = repo.findAll(); assertEquals(2, result.size()); assertEquals("Alice", result.get(0).getName()); } @Test void testSavePersistsPerson() { Person p = new Person(null, "Charlie"); doNothing().when(em).persist(p); Person result = repo.save(p); assertEquals("Charlie", result.getName()); verify(em).persist(p); } // Helper stub for TypedQuery static class TypedQueryStub<T> extends org.mockito.stubbing.Answer<T> implements jakarta.persistence.TypedQuery<T> { private final List<T> data; TypedQueryStub(List<T> data) { this.data = data; } @Override public List<T> getResultList() { return data; } // Implement other methods as needed with UnsupportedOperationException } } |
PersonResourceTest.java
1 2 3 4 5 6 7 8 9 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 |
package com.example.resource; import com.example.entity.Person; import com.example.repository.PersonRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.*; import java.util.List; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; class PersonResourceTest { @Mock PersonRepository repo; @InjectMocks PersonResource resource; @BeforeEach void setUp() { MockitoAnnotations.openMocks(this); } @Test void testGetAllReturnsPersons() { when(repo.findAll()).thenReturn(List.of(new Person(1L, "Alice"))); List<Person> persons = resource.getAll(); assertEquals(1, persons.size()); assertEquals("Alice", persons.get(0).getName()); } @Test void testGetAllReturnsEmptyList() { when(repo.findAll()).thenReturn(List.of()); List<Person> persons = resource.getAll(); assertTrue(persons.isEmpty()); } } |
Integration Tests
What does the integration test do?
Description:
The integration test (PersonResourceIT.java
) provides a full end-to-end verification of the secured REST API and OIDC authentication, as it would run in production. It does the following:
- Obtains a real OIDC access token from the local Open Liberty OIDC provider using the Resource Owner Password Credentials Grant (with
testuser
/testpass
). - Calls the secured REST API endpoint (
/api/persons
) with the access token, ensuring the API returns the expected data (Alice and Bob). - Attempts an unauthorized API call (no token), verifying that the API correctly rejects the request with a 401 Unauthorized response.
- Handles HTTPS with a self-signed certificate by configuring the HTTP client to trust all certificates (safe for local testing).
This test ensures:
- OIDC authentication is fully functional.
- The Liberty OIDC provider and client are correctly configured.
- The REST API is protected and only accessible with a valid token.
- The API returns the correct data from the database.
PersonResourceIT.java
1 2 3 4 5 6 7 8 9 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 |
package com.example; import org.junit.jupiter.api.*; import java.net.URI; import java.net.http.*; import java.util.regex.*; import static org.junit.jupiter.api.Assertions.*; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class PersonResourceIT { static String oidcToken; @BeforeAll static void obtainOidcToken() throws Exception { // Simulate OIDC Resource Owner Password Credentials Grant (for testuser/testpass) HttpClient client = HttpClient.newBuilder() .sslContext(TestUtils.trustAllSslContext()) // Accept self-signed cert .build(); String body = "grant_type=password&username=testuser&password=testpass&client_id=restapi-client&client_secret=test-secret&scope=openid"; HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://localhost:9443/oidc/endpoint/LocalOIDCProvider/token")) .header("Content-Type", "application/x-www-form-urlencoded") .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); assertEquals(200, response.statusCode(), "OIDC token endpoint should return 200"); Pattern p = Pattern.compile("\"access_token\"\\s*:\\s*\"([^\"]+)\""); Matcher m = p.matcher(response.body()); assertTrue(m.find(), "OIDC response should contain access_token"); oidcToken = m.group(1); assertNotNull(oidcToken); } @Test @Order(1) void testGetAllPersons() throws Exception { HttpClient client = HttpClient.newBuilder() .sslContext(TestUtils.trustAllSslContext()) .build(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://localhost:9443/api/persons")) .header("Authorization", "Bearer " + oidcToken) .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); assertEquals(200, response.statusCode()); assertTrue(response.body().contains("Alice")); assertTrue(response.body().contains("Bob")); } @Test @Order(2) void testUnauthorizedAccess() throws Exception { HttpClient client = HttpClient.newBuilder() .sslContext(TestUtils.trustAllSslContext()) .build(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://localhost:9443/api/persons")) .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); assertEquals(401, response.statusCode(), "Should be unauthorized without token"); } } |
TestUtils.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
package com.example; import javax.net.ssl.*; import java.security.SecureRandom; import java.security.cert.X509Certificate; public class TestUtils { public static SSLContext trustAllSslContext() { try { SSLContext ctx = SSLContext.getInstance("TLS"); ctx.init(null, new TrustManager[]{new X509TrustManager() { public void checkClientTrusted(X509Certificate[] xcs, String string) {} public void checkServerTrusted(X509Certificate[] xcs, String string) {} public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } }}, new SecureRandom()); return ctx; } catch (Exception e) { throw new RuntimeException(e); } } } |
Step 11: Build and Run Commands
- Default build & unit tests only:
1 2 |
mvn clean install |
Build, containerize, and run integration tests (all Podman steps controlled by Maven):
1 2 |
mvn clean install -Pintegration-test |
Troubleshooting
- Podman plugin errors: Ensure Podman is installed and accessible on your system.
- SSL/certificate errors: Integration test client trusts all certificates for local testing.
- Container conflicts: If containers already exist, stop and remove them before running tests.
- Integration tests fail: Check logs with
podman logs libertytest
andpodman logs pgtest
.
References
- Open Liberty OIDC Provider/Client Docs
- podman-maven-plugin documentation
- Open Liberty Podman Guide
- OpenID Connect Specification
You now have a fully automated, modern, secure Jakarta EE 10 REST API stack with end-to-end OIDC authentication, containerized and tested with Maven and Podman, and ready for further development or production hardening.