Adding persistence and security to the WildFly Batch-Processing Quickstart
Introduction
The WildFly batch-processing quickstart demonstrates Jakarta Batch (JSR-352) job execution using an embedded H2 database as its job repository. H2 is convenient for local development but is not suitable for production: it runs in-process, stores data in memory by default, and offers no network-level security.
This guide walks through four incremental improvements to the quickstart:
- PostgreSQL as the job repository: Replace the in-memory H2 database with a containerised PostgreSQL instance so that job execution data persists across server restarts.
- TLS for database connections: Encrypt traffic between WildFly and PostgreSQL, and reject any unencrypted connection attempts.
- Application authentication: Protect the web UI with an Elytron filesystem security realm so that only authorised users can access the application.
- HTTPS for the web application: Enable WildFly HTTPS listener so that all browser traffic is encrypted end to end.
Note: The post only covers the provisioned-server Maven profile.
Prerequisites
Before starting, ensure you have:
- JDK 21 or later.
- Maven 3.4 or later.
- Podman or Docker for running PostgreSQL.
Part 1: PostgreSQL Container with TLS
Encrypt the communication between the PostgreSQL database and WildFly with TLS.
Step 1: Generate TLS Certificates
Create a self-signed certificate that the PostgreSQL database presents to WildFly for verification.
NOTE: For production environments, ensure that you use only certificate authority (CA)-signed certificates.
Create a directory for certificates:
mkdir -p ~/postgres-certs
cd ~/postgres-certs
Generate the server private key and a self-signed certificate:
# Generate a private key
openssl genrsa -out server.key 2048
# Set restrictive permissions to meet PostgreSQL requirements
chmod 600 server.key
# Generate a self-signed certificate
openssl req -new -x509 -key server.key -out server.crt -days 365 \
-subj "/C=CA/ST=AB/L=Calgary/O=Organization/CN=localhost"
# Set permissions for the certificate
chmod 644 server.crt
Important: The Common Name (CN) must match how WildFly connects to PostgreSQL.
Step 2: Map Certificate Ownership into the User Namespace
Assign the ownership of the certificates to UID 999. This ownership assignment is required when running rootless Podman, as done in this procedure.
podman unshare chown 999:999 ~/postgres-certs/server.key ~/postgres-certs/server.crt
Confirm the result:
podman unshare ls -n ~/postgres-certs/
Expected output:
-rw-r--r--. 1 0 0 352 ... pg_hba.conf
-rw-r--r--. 1 0 0 409 ... postgresql.conf
-rw-r--r--. 1 999 999 1294 ... server.crt
-rw-------. 1 999 999 1704 ... server.key
Note that server.key and server.crt are now owned by the UID 999.
Important:
- You can no longer read
server.keyas your normal user. Usepodman unshare cat ~/postgres-certs/server.keyif you need to read it. - Re-run this step every time you regenerate the certificates.
Step 3: Create PostgreSQL Configuration Files
Create postgresql.conf to enable TLS:
cat > postgresql.conf << 'EOF'
# TLS/SSL Configuration
ssl = on
ssl_cert_file = '/var/lib/postgresql/server.crt'
ssl_key_file = '/var/lib/postgresql/server.key'
ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL'
ssl_prefer_server_ciphers = on
ssl_min_protocol_version = 'TLSv1.3'
# Connection Settings
listen_addresses = '*'
max_connections = 100
# Enable prepared transactions (required for XA if needed in future)
max_prepared_transactions = 100
EOF
Create pg_hba.conf to require SSL connections:
cat > pg_hba.conf << 'EOF'
# TYPE DATABASE USER ADDRESS METHOD
# Require SSL for all connections
hostssl all all 0.0.0.0/0 scram-sha-256
hostssl all all ::0/0 scram-sha-256
# Local connections
local all all trust
EOF
Step 4: Start PostgreSQL Container with TLS
Pull the PostgreSQL image:
podman pull docker.io/library/postgres:latest
Start the container with TLS enabled:
podman run -d \
--name postgres-jberet-secure \
-e POSTGRES_USER=batch_user \
-e POSTGRES_PASSWORD=SecurePass123! \
-e POSTGRES_DB=batch_db \
-v ~/postgres-certs/server.crt:/var/lib/postgresql/server.crt:z \
-v ~/postgres-certs/server.key:/var/lib/postgresql/server.key:z \
-v ~/postgres-certs/postgresql.conf:/var/lib/postgresql/postgresql.conf:z \
-v ~/postgres-certs/pg_hba.conf:/var/lib/postgresql/pg_hba.conf:z \
-p 5432:5432 \
postgres:16 \
-c config_file=/var/lib/postgresql/postgresql.conf \
-c hba_file=/var/lib/postgresql/pg_hba.conf
Step 5: Verify PostgreSQL TLS Configuration
Check that PostgreSQL accepts SSL connections:
podman exec postgres-jberet-secure psql -U batch_user -d batch_db -c "SHOW ssl;"
Expected output:
ssl
-----
on
(1 row)
Verify the TLS version:
podman exec -e PGPASSWORD='SecurePass123!' postgres-jberet-secure \
psql "host=127.0.0.1 port=5432 user=batch_user dbname=batch_db sslmode=require" \
-c "SELECT ssl, version AS tls_version, cipher
FROM pg_stat_ssl JOIN pg_stat_activity USING (pid)
WHERE pid = pg_backend_pid();"
Expected output:
ssl | tls_version | cipher
-----+-------------+------------------------
t | TLSv1.3 | TLS_AES_256_GCM_SHA384
(1 row)
Confirm that unencrypted connections are refused:
podman exec -e PGPASSWORD='SecurePass123!' postgres-jberet-secure \
psql "host=127.0.0.1 port=5432 user=batch_user dbname=batch_db sslmode=disable" -c "SELECT 1;"
Expected output:
psql: error: connection to server at "127.0.0.1", port 5432 failed: FATAL: no pg_hba.conf entry
for host "127.0.0.1", user "batch_user", database "batch_db", no encryption
The connection failure confirms that unencrypted connections are refused.
Part 2: WildFly Configuration for Provisioned Server
The provisioned-server profile packages a custom WildFly server using Maven.
Step 1: Navigate to the Quickstart
cd <quickstart_home>/batch-processing
Replace <quickstart_home> with the actual path to the quickstart root directory.
Step 2: Update pom.xml Dependencies
Change 1: Update the manifest entry at lines 201–202:
<manifestEntries>
<!-- Replace H2 with PostgreSQL JDBC driver -->
<Dependencies>org.postgresql.jdbc</Dependencies>
</manifestEntries>
Change 2: Update the provisioned-server profile add-ons at lines 224–226 to provision the server with a PostgreSQL module and a management CLI client:
<discover-provisioning-info>
<version>${version.server}</version>
<addOns>
<addOn>wildfly-cli</addOn>
<addOn>postgresql</addOn>
</addOns>
</discover-provisioning-info>
Change 3: Add packaging scripts after </discover-provisioning-info> to configure WildFly to use the PostgreSQL database for the batch-jberet subsystem:
<packaging-scripts>
<packaging-script>
<commands>
<!-- Create credential store for database credentials -->
<command>/subsystem=elytron/credential-store=batch-credentials:add(location=credentials/batch-store.jceks, relative-to=jboss.server.data.dir, credential-reference={clear-text=storePass123!}, create=true)</command>
<!-- Add database password to credential store -->
<command>/subsystem=elytron/credential-store=batch-credentials:add-alias(alias=db.password, secret-value=SecurePass123!)</command>
<!-- Configure PostgreSQL datasource with credential reference and TLS -->
<command>/subsystem=datasources/data-source=batch-processingDS:add( \
jndi-name=java:jboss/datasources/batch-processingDS, \
driver-name=postgresql, \
connection-url="jdbc:postgresql://localhost:5432/batch_db?ssl=true&sslmode=require", \
user-name=batch_user, \
credential-reference={store=batch-credentials, alias=db.password}, \
enabled=true, \
valid-connection-checker-class-name=org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLValidConnectionChecker, \
exception-sorter-class-name=org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLExceptionSorter, \
validate-on-match=true, \
background-validation=false)</command>
<!-- Configure JBeret JDBC job repository to use the same datasource -->
<command>/subsystem=batch-jberet/jdbc-job-repository=JSR352_JobRepository:add(data-source=batch-processingDS)</command>
<!-- Set as default job repository -->
<command>/subsystem=batch-jberet/:write-attribute(name=default-job-repository,value=JSR352_JobRepository)</command>
</commands>
</packaging-script>
</packaging-scripts>
Important Security Notes:
The credential store is protected by a password (storePass123!). In production environments, do not use a hardcoded password. Instead, use an external source, such as a HashiCorp vault, to supply the password.
Step 3: Configure SSL Trust for PostgreSQL Certificate
To trust the PostgreSQL self-signed certificate, import it into a truststore that WildFly uses.
Create the truststore:
cd ~/postgres-certs
# Import PostgreSQL certificate into a JKS truststore
keytool -import -trustcacerts -alias postgres-server \
-file server.crt \
-keystore pg-truststore.jks \
-storepass trustPass123! \
-noprompt
Add the following CLI command to the packaging scripts in pom.xml:
<packaging-scripts>
<packaging-script>
<commands>
<!-- Previous commands... -->
<!-- Configure SSL context for PostgreSQL certificate trust -->
<command>/subsystem=elytron/key-store=postgresql-truststore:add(path=pg-truststore.jks, relative-to=jboss.server.config.dir, credential-reference={clear-text=trustPass123!}, type=JKS)</command>
<command>/subsystem=elytron/trust-manager=postgresql-trust:add(key-store=postgresql-truststore)</command>
<command>/subsystem=elytron/client-ssl-context=postgresql-ssl:add(trust-manager=postgresql-trust, protocols=["TLSv1.3"])</command>
</commands>
</packaging-script>
</packaging-scripts>
Step 4: Remove Application-Level DataSource Definition
Open <quickstart_home>/batch-processing/src/main/java/org/jboss/as/quickstarts/batch/model/Contact.java.
Remove lines 25-30 (the @DataSourceDefinition annotation) so that the server-level datasource is used by both the application and the batch-jberet subsystem:
@DataSourceDefinition(name="java:jboss/datasources/batch-processingDS",
className="org.h2.jdbcx.JdbcDataSource",
url="jdbc:h2:mem:batch-processing;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=-1",
user="sa",
password="sa"
)
Step 5: Update Persistence Configuration
Open <quickstart_home>/batch-processing/src/main/resources/META-INF/persistence.xml.
Update the properties section to use PostgreSQL dialect:
<persistence-unit name="primary" transaction-type="JTA">
<jta-data-source>java:jboss/datasources/batch-processingDS</jta-data-source>
<properties>
<!-- Properties for Hibernate with PostgreSQL -->
<property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect" />
<property name="hibernate.hbm2ddl.auto" value="create-drop" />
<property name="hibernate.show_sql" value="false" />
</properties>
</persistence-unit>
Step 6: Build and Test Provisioned Server
Build the provisioned server:
cd <quickstart_home>/batch-processing
mvn clean package
Copy the truststore so that WildFly trusts the certificate presented by the PostgreSQL database:
cp ~/postgres-certs/pg-truststore.jks target/server/standalone/configuration/
Start the provisioned server:
target/server/bin/standalone.sh
Access the application:
http://localhost:8080/batch-processing/
Test the batch processing functionality:
- Click "Generate a new file and start import job"
- Click "Update jobs list" to verify the job completed
- Check server logs for successful database operations
Part 3: Authentication
Step 1: Define a Filesystem Realm to Add Application Users
Add the following user configuration to pom.xml (line 270), below </packaging-scripts>:
<command>/subsystem=elytron/filesystem-realm=exampleSecurityRealm:add(path=fs-realm-users,relative-to=jboss.server.config.dir)</command>
<command>/subsystem=elytron/filesystem-realm=exampleSecurityRealm:add-identity(identity=user1)</command>
<command>/subsystem=elytron/filesystem-realm=exampleSecurityRealm:set-password(identity=user1, clear={password="passwordUser1"})</command>
<command>/subsystem=elytron/filesystem-realm=exampleSecurityRealm:add-identity-attribute(identity=user1, name=Roles, value=["Admin","Guest"])</command>
<command>/subsystem=elytron/security-domain=exampleSecurityDomain:add(default-realm=exampleSecurityRealm,permission-mapper=default-permission-mapper,realms=[{realm=exampleSecurityRealm}])</command>
<command>/subsystem=undertow/application-security-domain=exampleApplicationSecurityDomain:add(security-domain=exampleSecurityDomain)</command>
<command>/subsystem=undertow:write-attribute(name=default-security-domain,value=exampleApplicationSecurityDomain)</command>
Note: You can use the Elytron credential store to securely store the password for user1 so that you do not need to include the plaintext password in the command. The steps to store the password in a credential store are similar to those used in Step 2, Change 3 of Part 2: WildFly Configuration for Provisioned Server of the guide. The steps have been omitted here for brevity.
Step 2: Configure Security in web.xml
Create a file <quickstart_home>/batch-processing/src/main/webapp/WEB-INF/web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app 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/web-app_6_0.xsd"
version="6.0">
<security-constraint>
<web-resource-collection>
<web-resource-name>all</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<!-- Define the role that can access the protected resource -->
<auth-constraint>
<role-name>Admin</role-name>
<!-- To disable authentication you can use the wildcard *
To authenticate but allow any role, use the wildcard **. -->
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>exampleSecurityRealm</realm-name>
</login-config>
</web-app>
Step 3: Map Security Roles
Create <quickstart_home>/batch-processing/src/main/webapp/WEB-INF/jboss-web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<jboss-web xmlns="http://www.jboss.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.jboss.com/xml/ns/javaee
http://www.jboss.org/schema/jbossas/jboss-web_14_0.xsd">
<security-domain>exampleApplicationSecurityDomain</security-domain>
</jboss-web>
Step 4: Build and Test Provisioned Server
Build the provisioned server:
cd <quickstart_home>/batch-processing
mvn clean package
Copy the truststore so that WildFly trusts the certificate presented by the PostgreSQL database:
cp ~/postgres-certs/pg-truststore.jks target/server/standalone/configuration/
Start the provisioned server:
target/server/bin/standalone.sh
Access the application:
http://localhost:8080/batch-processing/
Log in with the credentials:
- username:
user1 - password:
passwordUser1
Test the batch processing functionality:
- Click "Generate a new file and start import job"
- Click "Update jobs list" to verify the job completed
- Check server logs for successful database operations
Part 4: HTTPS for Web Application
Step 1: Generate a certificate
Create certificates for TLS:
mkdir -p ~/wildfly-certs
cd ~/wildfly-certs
# Generate keystore with self-signed certificate
keytool -genkeypair -alias wildfly-https \
-keyalg RSA -keysize 2048 \
-validity 365 \
-keystore wildfly-https.jks \
-storepass httpsPass123! \
-keypass httpsPass123! \
-dname "CN=localhost, OU=Dev, O=Organization, L=TO, ST=ON, C=CA"
Step 2: Configure HTTPS in WildFly
Add these commands to the packaging scripts in pom.xml for the provisioned server:
<!-- Configure HTTPS -->
<command>/subsystem=elytron/key-store=https-keystore:add(path=~/wildfly-certs/wildfly-https.jks, credential-reference={clear-text=httpsPass123!}, type=JKS)</command>
<command>/subsystem=elytron/key-manager=https-key-manager:add(key-store=https-keystore, credential-reference={clear-text=httpsPass123!})</command>
<command>/subsystem=elytron/server-ssl-context=https-ssl-context:add(key-manager=https-key-manager, protocols=["TLSv1.3"])</command>
<command>/subsystem=undertow/server=default-server/https-listener=https:add(socket-binding=https, ssl-context=https-ssl-context, enable-http2=true)</command>
Step 3: Build and Test Provisioned Server
Build the provisioned server:
cd <quickstart_home>/batch-processing
mvn clean package
Copy the truststore so that WildFly trusts the certificate presented by the PostgreSQL database:
cp ~/postgres-certs/pg-truststore.jks target/server/standalone/configuration/
Start the provisioned server:
target/server/bin/standalone.sh
Access the application over HTTPS:
https://localhost:8443/batch-processing/
Note: Your browser will warn about the self-signed certificate.
Log in with the credentials:
- username:
user1 - password:
passwordUser1
Test the batch processing functionality:
- Click "Generate a new file and start import job"
- Click "Update jobs list" to verify the job completed
- Check server logs for successful database operations
Conclusion
The quickstart now uses TLS both when communicating with the database and the user's web browser. Additionally, only the user user1 can access the application. Finally, because the quickstart uses a separate PostgreSQL database instead of H2 database, the data is persisted over server restarts.