In this section, we define all of the application- and database-level configuration files required to wire up our Spring Boot app with H2/PostgreSQL, JPA, Liquibase changelogs, initial data loads, and JWE key properties.
application.yml
Holds Spring datasource, H2 console, JPA/Hibernate, Liquibase changelog path, and all JWT/JWE key, issuer, and expiration settings.
db/master.xml
The Liquibase master changelog, with includes and DBMS-specific properties for H2 and PostgreSQL.
db/changelog/changelog-user.xml
Your core schema changelog defining user_identity, authority, and user_authority_mapping tables, indexes, FKs, and initial <loadData> steps.
db/data/user.csv
Initial user records (UUID, username, bcrypt-hashed password, email, enabled flag, timestamps, auditor).
db/data/authority.csv
Initial authority records (UUID, name, description, timestamps, auditor).
SecurityConfig: Integrates DomainUserDetailsService, configures authentication manager, password encoder, and stateless security filter chain with JWE support.
DatabaseConfig: Enables JPA repositories, auditing, and transaction management.
SecurityJwtConfig
SecurityConfig
JwtProperties
DatabaseConfig
🛠️ Step 4: JPA Integration
In this section, we define the JPA entities representing users, authorities, and their mappings, along with the Spring Data repository for loading users with their authorities.
Authority: authority table entity storing role data.
User: user_identity table entity storing user credentials and profile.
UserAuthorityMapping: user_authority_mapping join table entity linking users and authorities.
UserAuthorityMappingId: Composite key class for UserAuthorityMapping.
UserRepository: Spring Data JPA repository for User with an entity graph to load authorities.
BaseEntity
Authority
User
UserAuthorityMapping
UserAuthorityMappingId
UserRepository
🛠️ Step 5: Secure JWE Token Utilities
In this section, we define the core utility classes and constants needed to generate, encrypt, and resolve JSON Web Encryption (JWE) tokens in your Spring Boot application, integrate auditing, and implement a JPA-based user details service:
AuthoritiesConstants: Centralize role names with the ROLE_ prefix.
CookieBearerTokenResolver: Resolve bearer tokens from Authorization headers or HTTP cookies.
CookieUtils: Create HTTP-only, secure cookies for access tokens.
JweUtil: Sign (JWS) and encrypt (JWE) JWTs using RSA keys and Nimbus.
KeyUtils: Build RSA JWKs from PEM‐encoded key material.
SecurityUtils: Extract the current user’s login from the security context.
SpringSecurityAuditorAware: Implement AuditorAware to provide the current user for auditing.
DomainUserDetailsService: JPA-based UserDetailsService loading user and authorities for authentication.
These utilities form the foundation for a stateless, JWE‐based authentication flow in Spring Security.
AuthoritiesConstants
CookieBearerTokenResolver
CookieUtils
JweUtil
KeyUtils
SecurityUtils
DomainUserDetailsService
SpringSecurityAuditorAware
🛠️ Step 6: Authentication & Protected Endpoints
In this section, we define the REST controllers and DTOs necessary for:
AuthController: Authenticate users, issue JWE tokens, and set secure cookies.
HelloController: Expose protected resource endpoints for authenticated users and admin-specific paths.
LoginRequestDTO: Model the login request payload (username/password).
TokenDTO: Model the authentication response including token and expiration.
These components complete the stateless authentication flow by handling login, token issuance, cookie management, and resource protection.
In this section, we expose REST controllers and DTOs to handle user authentication, token issuance, and protected resource access.
AuthController
HelloController
LoginRequestDTO
TokenDTO
▶️ Run the App
BASH
./mvnw spring-boot:run
# or
gradle bootRun
🧪 Test Endpoints
In this section, we clarify Test Endpoints and summarize the key points you will apply in implementation.
Admin Flow
Login as admin and capture the JWE token from the Set-Cookie header:
You now have a practical Spring Boot JWE Authentication with JPA implementation with a clear, production-friendly Spring Boot structure. As a next step, adapt configuration and tests to your own domain, then validate behavior under realistic traffic and failure scenarios.
package io.github.susimsek.springbootjweauthjpademo.security;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class AuthoritiesConstants {
public static final String ADMIN = "ROLE_ADMIN";
public static final String USER = "ROLE_USER";
public static final String ANONYMOUS = "ROLE_ANONYMOUS";
}
KOTLINAuthoritiesConstants.kt
package io.github.susimsek.springbootjweauthjpademo.security
object AuthoritiesConstants {
const val ADMIN = "ROLE_ADMIN"
const val USER = "ROLE_USER"
const val ANONYMOUS = "ROLE_ANONYMOUS"
}
package io.github.susimsek.springbootjweauthjpademo.controller;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.web.bind.annotation.*;
import static io.github.susimsek.springbootjweauthjpademo.security.SecurityUtils.AUTHORITIES_KEY;
@RestController
@RequestMapping("/api/hello")
public class HelloController {
@GetMapping
public String helloAll(@AuthenticationPrincipal Jwt jwt) {
String user = jwt.getSubject();
var roles = jwt.getClaimAsStringList(AUTHORITIES_KEY);
return "Hello, " + user + "! Your roles: " + roles;
}
@GetMapping("/admin")
public String helloAdmin(@AuthenticationPrincipal Jwt jwt) {
return "Hello Admin, " + jwt.getSubject() + "!";
}
}
KOTLINHelloController.kt
package io.github.susimsek.springbootjweauthjpademo.controller
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.security.oauth2.jwt.Jwt
import org.springframework.web.bind.annotation.*
import io.github.susimsek.springbootjweauthjpademo.security.SecurityUtils.AUTHORITIES_KEY
@RestController
@RequestMapping("/api/hello")
class HelloController {
@GetMapping
fun helloAll(@AuthenticationPrincipal jwt: Jwt): String {
val user = jwt.subject
val roles = jwt.getClaimAsStringList(AUTHORITIES_KEY)
return "Hello, \$user! Your roles: \$roles"
}
@GetMapping("/admin")
fun helloAdmin(@AuthenticationPrincipal jwt: Jwt): String {
return "Hello Admin, \${jwt.subject}!"
}
}
JAVALoginRequestDTO.java
package io.github.susimsek.springbootjweauthjpademo.dto;
public record LoginRequestDTO(
String username,
String password
) { }
KOTLINLoginRequestDTO.kt
package io.github.susimsek.springbootjweauthjpademo.dto
data class LoginRequestDTO(
val username: String,
val password: String
)
JAVATokenDTO.java
package io.github.susimsek.springbootjweauthjpademo.dto;
public record TokenDTO(
String accessToken,
String tokenType,
long accessTokenExpiresIn
) {}
KOTLINTokenDTO.kt
package io.github.susimsek.springbootjweauthjpademo.dto
import kotlin.Long
data class TokenDTO(
val accessToken: String,
val tokenType: String,
val accessTokenExpiresIn: Long
)