A Jakarta EE application demonstrating stateless authentication using JSON Web Tokens (JWT) backed by a database IdentityStore and JPA persistence.
- Java 11+, Maven
- Jakarta EE–compliant server (WildFly, Payara)
- MySQL or other RDBMS
-
Create DB & Tables:
CREATE DATABASE j2ee_security_db; USE j2ee_security_db;
-
Seed data with bcrypt-hashed passwords.
-
JDBC DataSource: configure in your server, e.g. WildFly
standalone.xml:<datasource jndi-name="java:/jdbc/JwtDS" pool-name="JwtPool"> <connection-url>jdbc:mysql://localhost:3306/jwt_security</connection-url> <driver>mysql</driver> <security><user-name>dbuser</user-name><password>dbpass</password></security> </datasource>
Place under src/main/resources/META-INF/persistence.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns="https://jakarta.ee/xml/ns/persistence" version="3.0">
<persistence-unit name="j2eeSecuredAppDB" transaction-type="JTA">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<jta-data-source>j2ee_security_db</jta-data-source>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
<property name="hibernate.transaction.jta.platform" value="org.hibernate.engine.transaction.jta.platform.internal.SunOneJtaPlatform"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="hibernate.show_sql" value="true"/>
</properties>
</persistence-unit>
</persistence>- JDBC Pooling: Reuses DB connections for efficiency.
- JPA & JTA: Container-managed transactions ensure ACID integrity.
- IdentityStore: Centralizes user/role lookups from the database.
- JWT: Stateless tokens carrying claims (e.g.,
sub,roles,exp), signed with HMAC-SHA256 to prevent tampering.
src/main/java/com/tharindu/jwt/
├── config/
│ └── AppConfig.java
├── controller/
│ └── AuthController.java
├── DTO/
│ └── Credentials.java
├── Security/
│ ├── AuthMechanism.java
│ └── AppIdentityStore.java
├── service/
│ └── UserService.java
├── servlet/
│ ├── Login.java
│ └── Profile.java
├── Entity/
│ └── User.java
└── Util/
└── JWUtil.java
src/main/resources/
└── META-INF/persistence.xml
src/main/webapp/
├── login.jsp
├── index.jsp
├── home.jsp
└── WEB-INF/
└── web.xml # Security constraints and filter mapping
@Override
public CredentialValidationResult validate(Credential credential) {
System.out.println("Validating credential: " + credential);
if (credential instanceof UsernamePasswordCredential) {
UsernamePasswordCredential UPC = (UsernamePasswordCredential) credential;
if (loginService.validate(UPC.getCaller(), UPC.getPasswordAsString())) {
Set<String> roles = loginService.getRoles(UPC.getCaller());
return new CredentialValidationResult(UPC.getCaller(), roles);
}
}
// return IdentityStore.super.validate(credential);
return CredentialValidationResult.INVALID_RESULT;
} public static String generateToken(String username, Set<String> roles) {
return Jwts.builder()
.subject(username)
.claim("roles", roles)
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SECRET_KEY,Jwts.SIG.HS256)
.compact();
} @Override
public AuthenticationStatus validateRequest(HttpServletRequest Request, HttpServletResponse Response, HttpMessageContext Context) throws AuthenticationException {
String authHeader = Request.getHeader(HttpHeaders.AUTHORIZATION);
if (authHeader != null && !authHeader.startsWith("Bearer ")) {
try {
String token = authHeader.substring(7); // Extract the token
Claims claims = JWTUtil.parseToken(token).getPayload();
String username = claims.getSubject();
List roles = claims.get("roles", List.class);
CredentialValidationResult CVR = new CredentialValidationResult(username, new HashSet<>(roles));
return Context.notifyContainerAboutLogin(CVR);
}catch (JwtException JE){
return Context.responseUnauthorized();
}
}
}-
Login: POST
/loginwith JSON body:{"username":"admin","password":"Secret123"}- Response:
200 OKwith JSON{ "token": "<JWT>" }.
- Response:
-
Access Protected: GET
/api/protectedwith header:Authorization: Bearer <JWT_TOKEN>- Success:
200 OKwith resource payload. - Failure:
401 Unauthorizedif token is invalid or expired.
- Success:
- web.xml: Map
JWTAuthenticationFilterto/api/*, leave/loginopen. - beans.xml: Register filter if using CDI discovery.
Design not created -- Focusing oin BackendFork the repo, create a feature branch, and open a PR. Ensure JWT and IdentityStore tests are included.
MIT © 2025 Tharindu714
Stateless, scalable security with JWT and JPA in Jakarta EE! 🚀