Deactivating keys

This commit is contained in:
Dario Ghunney Ware 2025-07-29 20:56:26 +01:00
parent 72d0339588
commit 9d162abc8c
20 changed files with 177 additions and 146 deletions

View File

@ -304,8 +304,8 @@ public class ApplicationProperties {
private boolean enableKeystore = true; private boolean enableKeystore = true;
private boolean enableKeyRotation = false; private boolean enableKeyRotation = false;
private boolean enableKeyCleanup = true; private boolean enableKeyCleanup = true;
private int keyRetentionDays = 7; private int keyRetentionDays;
private int cleanupBatchSize = 100; private int cleanupBatchSize;
} }
} }

View File

@ -62,9 +62,12 @@ security:
jwt: jwt:
enableKeyStore: true # Set to 'true' to enable JWT key store enableKeyStore: true # Set to 'true' to enable JWT key store
enableKeyRotation: true # Set to 'true' to enable JWT key rotation enableKeyRotation: true # Set to 'true' to enable JWT key rotation
enableKeyCleanup: true # Set to 'true' to enable JWT key cleanup
keyRetentionDays: 7 # Number of days to retain old keys
cleanupBatchSize: 100 # Number of keys to clean up in each batch
premium: premium:
key: 3R3T-WFPY-UNRW-LJFA-MMXM-YVJK-WCKY-PCRT # fixme: remove key: 00000000-0000-0000-0000-000000000000
enabled: false # Enable license key checks for pro/enterprise features enabled: false # Enable license key checks for pro/enterprise features
proFeatures: proFeatures:
SSOAutoLogin: false SSOAutoLogin: false

View File

@ -58,7 +58,7 @@ public class CustomAuthenticationSuccessHandler
String jwt = String jwt =
jwtService.generateToken( jwtService.generateToken(
authentication, Map.of("authType", AuthenticationType.WEB)); authentication, Map.of("authType", AuthenticationType.WEB));
jwtService.addTokenToResponse(response, jwt); jwtService.addToken(response, jwt);
log.debug("JWT generated for user: {}", userName); log.debug("JWT generated for user: {}", userName);
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to generate JWT token for user: {}", userName, e); log.error("Failed to generate JWT token for user: {}", userName, e);

View File

@ -71,8 +71,8 @@ public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
authentication.getClass().getSimpleName()); authentication.getClass().getSimpleName());
getRedirectStrategy().sendRedirect(request, response, LOGOUT_PATH); getRedirectStrategy().sendRedirect(request, response, LOGOUT_PATH);
} }
} else if (!jwtService.extractTokenFromRequest(request).isBlank()) { } else if (!jwtService.extractToken(request).isBlank()) {
jwtService.clearTokenFromResponse(response); jwtService.clearToken(response);
getRedirectStrategy().sendRedirect(request, response, LOGOUT_PATH); getRedirectStrategy().sendRedirect(request, response, LOGOUT_PATH);
} else { } else {
// Redirect to login page after logout // Redirect to login page after logout

View File

@ -16,20 +16,18 @@ import stirling.software.proprietary.security.model.JwtSigningKey;
@Repository @Repository
public interface JwtSigningKeyRepository extends JpaRepository<JwtSigningKey, Long> { public interface JwtSigningKeyRepository extends JpaRepository<JwtSigningKey, Long> {
Optional<JwtSigningKey> findByIsActiveTrue(); Optional<JwtSigningKey> findFirstByIsActiveTrueOrderByCreatedAtDesc();
Optional<JwtSigningKey> findByKeyId(String keyId); Optional<JwtSigningKey> findByKeyId(String keyId);
@Query( @Query("SELECT k FROM JwtSigningKey k WHERE k.createdAt < :cutoffDate ORDER BY k.createdAt ASC")
"SELECT k FROM signing_keys k WHERE k.isActive = false AND k.createdAt < :cutoffDate ORDER BY k.createdAt ASC") List<JwtSigningKey> findKeysOlderThan(
List<JwtSigningKey> findInactiveKeysOlderThan(
@Param("cutoffDate") LocalDateTime cutoffDate, Pageable pageable); @Param("cutoffDate") LocalDateTime cutoffDate, Pageable pageable);
@Query( @Query("SELECT COUNT(k) FROM JwtSigningKey k WHERE k.createdAt < :cutoffDate")
"SELECT COUNT(k) FROM signing_keys k WHERE k.isActive = false AND k.createdAt < :cutoffDate")
long countKeysEligibleForCleanup(@Param("cutoffDate") LocalDateTime cutoffDate); long countKeysEligibleForCleanup(@Param("cutoffDate") LocalDateTime cutoffDate);
@Modifying @Modifying
@Query("DELETE FROM signing_keys k WHERE k.id IN :ids") @Query("DELETE FROM JwtSigningKey k WHERE k.id IN :ids")
void deleteAllByIdInBatch(@Param("ids") List<Long> ids); void deleteAllByIdInBatch(@Param("ids") List<Long> ids);
} }

View File

@ -68,7 +68,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
return; return;
} }
String jwtToken = jwtService.extractTokenFromRequest(request); String jwtToken = jwtService.extractToken(request);
if (jwtToken == null) { if (jwtToken == null) {
// If they are unauthenticated and navigating to '/', redirect to '/login' instead of // If they are unauthenticated and navigating to '/', redirect to '/login' instead of
@ -89,19 +89,19 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
jwtService.validateToken(jwtToken); jwtService.validateToken(jwtToken);
} catch (AuthenticationFailureException e) { } catch (AuthenticationFailureException e) {
// Clear invalid tokens from response // Clear invalid tokens from response
jwtService.clearTokenFromResponse(response); jwtService.clearToken(response);
handleAuthenticationFailure(request, response, e); handleAuthenticationFailure(request, response, e);
return; return;
} }
Map<String, Object> claims = jwtService.extractAllClaims(jwtToken); Map<String, Object> claims = jwtService.extractClaims(jwtToken);
String tokenUsername = claims.get("sub").toString(); String tokenUsername = claims.get("sub").toString();
try { try {
Authentication authentication = createAuthentication(request, claims); Authentication authentication = createAuthentication(request, claims);
String jwt = jwtService.generateToken(authentication, claims); String jwt = jwtService.generateToken(authentication, claims);
jwtService.addTokenToResponse(response, jwt); jwtService.addToken(response, jwt);
} catch (SQLException | UnsupportedProviderException e) { } catch (SQLException | UnsupportedProviderException e) {
log.error("Error processing user authentication for user: {}", tokenUsername, e); log.error("Error processing user authentication for user: {}", tokenUsername, e);
handleAuthenticationFailure( handleAuthenticationFailure(

View File

@ -76,7 +76,7 @@ public class CustomOAuth2AuthenticationSuccessHandler
String jwt = String jwt =
jwtService.generateToken( jwtService.generateToken(
authentication, Map.of("authType", AuthenticationType.OAUTH2)); authentication, Map.of("authType", AuthenticationType.OAUTH2));
jwtService.addTokenToResponse(response, jwt); jwtService.addToken(response, jwt);
} }
if (userService.isUserDisabled(username)) { if (userService.isUserDisabled(username)) {
getRedirectStrategy() getRedirectStrategy()

View File

@ -141,7 +141,7 @@ public class CustomSaml2AuthenticationSuccessHandler
String jwt = String jwt =
jwtService.generateToken( jwtService.generateToken(
authentication, Map.of("authType", AuthenticationType.SAML2)); authentication, Map.of("authType", AuthenticationType.SAML2));
jwtService.addTokenToResponse(response, jwt); jwtService.addToken(response, jwt);
} }
} }
} }

View File

@ -68,7 +68,7 @@ public class JwtSaml2AuthenticationRequestRepository
return null; return null;
} }
Map<String, Object> claims = jwtService.extractAllClaims(token); Map<String, Object> claims = jwtService.extractClaims(token);
return deserializeSamlRequest(claims); return deserializeSamlRequest(claims);
} }

View File

@ -42,14 +42,14 @@ public class JwtKeyCleanupService {
} }
@Transactional @Transactional
@Scheduled(fixedDelay = 1, timeUnit = TimeUnit.MINUTES) @Scheduled(fixedDelay = 24, timeUnit = TimeUnit.HOURS)
public void cleanup() { public void cleanup() {
if (!jwtProperties.isEnableKeyCleanup() || !keystoreService.isKeystoreEnabled()) { if (!jwtProperties.isEnableKeyCleanup() || !keystoreService.isKeystoreEnabled()) {
log.debug("Key cleanup is disabled, skipping cleanup"); log.debug("Key cleanup is disabled");
return; return;
} }
log.info("Removing inactive keys older than {} days", jwtProperties.getKeyRetentionDays()); log.info("Removing keys older than {} day(s)", jwtProperties.getKeyRetentionDays());
try { try {
LocalDateTime cutoffDate = LocalDateTime cutoffDate =
@ -75,7 +75,7 @@ public class JwtKeyCleanupService {
while (true) { while (true) {
Pageable pageable = PageRequest.of(0, batchSize); Pageable pageable = PageRequest.of(0, batchSize);
List<JwtSigningKey> keysToCleanup = List<JwtSigningKey> keysToCleanup =
signingKeyRepository.findInactiveKeysOlderThan(cutoffDate, pageable); signingKeyRepository.findKeysOlderThan(cutoffDate, pageable);
if (keysToCleanup.isEmpty()) { if (keysToCleanup.isEmpty()) {
break; break;

View File

@ -20,6 +20,7 @@ import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
@ -35,7 +36,7 @@ import stirling.software.proprietary.security.model.JwtSigningKey;
public class JwtKeystoreService implements JwtKeystoreServiceInterface { public class JwtKeystoreService implements JwtKeystoreServiceInterface {
public static final String KEY_SUFFIX = ".key"; public static final String KEY_SUFFIX = ".key";
private final JwtSigningKeyRepository repository; private final JwtSigningKeyRepository signingKeyRepository;
private final ApplicationProperties.Security.Jwt jwtProperties; private final ApplicationProperties.Security.Jwt jwtProperties;
private volatile KeyPair currentKeyPair; private volatile KeyPair currentKeyPair;
@ -43,8 +44,9 @@ public class JwtKeystoreService implements JwtKeystoreServiceInterface {
@Autowired @Autowired
public JwtKeystoreService( public JwtKeystoreService(
JwtSigningKeyRepository repository, ApplicationProperties applicationProperties) { JwtSigningKeyRepository signingKeyRepository,
this.repository = repository; ApplicationProperties applicationProperties) {
this.signingKeyRepository = signingKeyRepository;
this.jwtProperties = applicationProperties.getSecurity().getJwt(); this.jwtProperties = applicationProperties.getSecurity().getJwt();
} }
@ -64,7 +66,7 @@ public class JwtKeystoreService implements JwtKeystoreServiceInterface {
} }
@Override @Override
public KeyPair getActiveKeypair() { public KeyPair getActiveKeyPair() {
if (!isKeystoreEnabled() || currentKeyPair == null) { if (!isKeystoreEnabled() || currentKeyPair == null) {
return generateRSAKeypair(); return generateRSAKeypair();
} }
@ -72,20 +74,25 @@ public class JwtKeystoreService implements JwtKeystoreServiceInterface {
} }
@Override @Override
public Optional<KeyPair> getKeypairByKeyId(String keyId) { public Optional<KeyPair> getKeyPairByKeyId(String keyId) {
if (!isKeystoreEnabled()) { if (!isKeystoreEnabled()) {
log.debug("Keystore is disabled, cannot lookup key by ID: {}", keyId);
return Optional.empty(); return Optional.empty();
} }
try { try {
Optional<JwtSigningKey> signingKey = repository.findByKeyId(keyId); log.debug("Looking up signing key in database for keyId: {}", keyId);
Optional<JwtSigningKey> signingKey = signingKeyRepository.findByKeyId(keyId);
if (signingKey.isEmpty()) { if (signingKey.isEmpty()) {
log.warn("No signing key found in database for keyId: {}", keyId);
return Optional.empty(); return Optional.empty();
} }
log.debug("Found signing key in database, loading private key for keyId: {}", keyId);
PrivateKey privateKey = loadPrivateKey(keyId); PrivateKey privateKey = loadPrivateKey(keyId);
PublicKey publicKey = decodePublicKey(signingKey.get().getSigningKey()); PublicKey publicKey = decodePublicKey(signingKey.get().getSigningKey());
log.debug("Successfully loaded key pair for keyId: {}", keyId);
return Optional.of(new KeyPair(publicKey, privateKey)); return Optional.of(new KeyPair(publicKey, privateKey));
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to load keypair for keyId: {}", keyId, e); log.error("Failed to load keypair for keyId: {}", keyId, e);
@ -104,7 +111,8 @@ public class JwtKeystoreService implements JwtKeystoreServiceInterface {
} }
private void loadOrGenerateKeypair() { private void loadOrGenerateKeypair() {
Optional<JwtSigningKey> activeKey = repository.findByIsActiveTrue(); Optional<JwtSigningKey> activeKey =
signingKeyRepository.findFirstByIsActiveTrueOrderByCreatedAtDesc();
if (activeKey.isPresent()) { if (activeKey.isPresent()) {
try { try {
@ -112,9 +120,10 @@ public class JwtKeystoreService implements JwtKeystoreServiceInterface {
PrivateKey privateKey = loadPrivateKey(currentKeyId); PrivateKey privateKey = loadPrivateKey(currentKeyId);
PublicKey publicKey = decodePublicKey(activeKey.get().getSigningKey()); PublicKey publicKey = decodePublicKey(activeKey.get().getSigningKey());
currentKeyPair = new KeyPair(publicKey, privateKey); currentKeyPair = new KeyPair(publicKey, privateKey);
log.info("Loaded existing keypair with keyId: {}", currentKeyId);
log.info("Loaded existing keypair: {}", currentKeyId);
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to load existing keypair, generating new one", e); log.error("Failed to load existing keypair, generating new keypair", e);
generateAndStoreKeypair(); generateAndStoreKeypair();
} }
} else { } else {
@ -122,6 +131,7 @@ public class JwtKeystoreService implements JwtKeystoreServiceInterface {
} }
} }
@Transactional
private void generateAndStoreKeypair() { private void generateAndStoreKeypair() {
try { try {
KeyPair keyPair = generateRSAKeypair(); KeyPair keyPair = generateRSAKeypair();
@ -131,12 +141,12 @@ public class JwtKeystoreService implements JwtKeystoreServiceInterface {
JwtSigningKey signingKey = JwtSigningKey signingKey =
new JwtSigningKey(keyId, encodePublicKey(keyPair.getPublic()), "RS256"); new JwtSigningKey(keyId, encodePublicKey(keyPair.getPublic()), "RS256");
repository.save(signingKey); signingKeyRepository.save(signingKey);
currentKeyPair = keyPair; currentKeyPair = keyPair;
currentKeyId = keyId; currentKeyId = keyId;
log.info("Generated and stored new keypair with keyId: {}", keyId); log.info("Generated and stored new keypair with keyId: {}", keyId);
} catch (Exception e) { } catch (IOException e) {
log.error("Failed to generate and store keypair", e); log.error("Failed to generate and store keypair", e);
throw new RuntimeException("Keypair generation failed", e); throw new RuntimeException("Keypair generation failed", e);
} }
@ -155,6 +165,12 @@ public class JwtKeystoreService implements JwtKeystoreServiceInterface {
return keyPairGenerator.generateKeyPair(); return keyPairGenerator.generateKeyPair();
} }
@Override
public KeyPair refreshKeyPairs() {
generateAndStoreKeypair();
return currentKeyPair;
}
private String generateKeyId() { private String generateKeyId() {
return "jwt-key-" return "jwt-key-"
+ LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HHmmss")); + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HHmmss"));

View File

@ -5,11 +5,13 @@ import java.util.Optional;
public interface JwtKeystoreServiceInterface { public interface JwtKeystoreServiceInterface {
KeyPair getActiveKeypair(); KeyPair getActiveKeyPair();
Optional<KeyPair> getKeypairByKeyId(String keyId); Optional<KeyPair> getKeyPairByKeyId(String keyId);
String getActiveKeyId(); String getActiveKeyId();
boolean isKeystoreEnabled(); boolean isKeystoreEnabled();
KeyPair refreshKeyPairs();
} }

View File

@ -1,6 +1,7 @@
package stirling.software.proprietary.security.service; package stirling.software.proprietary.security.service;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.PublicKey;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -40,7 +41,7 @@ public class JwtService implements JwtServiceInterface {
private static final String AUTHORIZATION_HEADER = "Authorization"; private static final String AUTHORIZATION_HEADER = "Authorization";
private static final String BEARER_PREFIX = "Bearer "; private static final String BEARER_PREFIX = "Bearer ";
private static final String ISSUER = "Stirling PDF"; private static final String ISSUER = "Stirling PDF";
private static final long EXPIRATION = 3600000; private static final long EXPIRATION = 300000; // 5 minutes in milliseconds
private final JwtKeystoreServiceInterface keystoreService; private final JwtKeystoreServiceInterface keystoreService;
private final boolean v2Enabled; private final boolean v2Enabled;
@ -71,7 +72,7 @@ public class JwtService implements JwtServiceInterface {
@Override @Override
public String generateToken(String username, Map<String, Object> claims) { public String generateToken(String username, Map<String, Object> claims) {
KeyPair keyPair = keystoreService.getActiveKeypair(); KeyPair keyPair = keystoreService.getActiveKeyPair();
var builder = var builder =
Jwts.builder() Jwts.builder()
@ -92,7 +93,7 @@ public class JwtService implements JwtServiceInterface {
@Override @Override
public void validateToken(String token) throws AuthenticationFailureException { public void validateToken(String token) throws AuthenticationFailureException {
extractAllClaimsFromToken(token); extractAllClaims(token);
if (isTokenExpired(token)) { if (isTokenExpired(token)) {
throw new AuthenticationFailureException("The token has expired"); throw new AuthenticationFailureException("The token has expired");
@ -105,8 +106,8 @@ public class JwtService implements JwtServiceInterface {
} }
@Override @Override
public Map<String, Object> extractAllClaims(String token) { public Map<String, Object> extractClaims(String token) {
Claims claims = extractAllClaimsFromToken(token); Claims claims = extractAllClaims(token);
return new HashMap<>(claims); return new HashMap<>(claims);
} }
@ -120,29 +121,37 @@ public class JwtService implements JwtServiceInterface {
} }
private <T> T extractClaim(String token, Function<Claims, T> claimsResolver) { private <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaimsFromToken(token); final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims); return claimsResolver.apply(claims);
} }
private Claims extractAllClaimsFromToken(String token) { private Claims extractAllClaims(String token) {
try { try {
// Extract key ID from token header if present // Extract key ID from token header if present
String keyId = extractKeyIdFromToken(token); String keyId = extractKeyId(token);
KeyPair keyPair; KeyPair keyPair;
if (keyId != null) { if (keyId != null) {
Optional<KeyPair> specificKeyPair = keystoreService.getKeypairByKeyId(keyId); log.debug("Looking up key pair for key ID: {}", keyId);
Optional<KeyPair> specificKeyPair = keystoreService.getKeyPairByKeyId(keyId);
if (specificKeyPair.isPresent()) { if (specificKeyPair.isPresent()) {
keyPair = specificKeyPair.get(); keyPair = specificKeyPair.get();
log.debug("Successfully found key pair for key ID: {}", keyId);
} else { } else {
log.warn( log.warn(
"Key ID {} not found in keystore, token may have been signed with a rotated key", "Key ID {} not found in keystore, token may have been signed with a rotated key",
keyId); keyId);
throw new AuthenticationFailureException( if (keystoreService.getActiveKeyId().equals(keyId)) {
"JWT token signed with unknown key ID: " + keyId); log.debug("Rotating key pairs");
keystoreService.refreshKeyPairs();
}
keyPair = keystoreService.getActiveKeyPair();
} }
} else { } else {
keyPair = keystoreService.getActiveKeypair(); log.debug("No key ID in token header, using active key pair");
keyPair = keystoreService.getActiveKeyPair();
} }
return Jwts.parser() return Jwts.parser()
@ -169,7 +178,7 @@ public class JwtService implements JwtServiceInterface {
} }
@Override @Override
public String extractTokenFromRequest(HttpServletRequest request) { public String extractToken(HttpServletRequest request) {
String authHeader = request.getHeader(AUTHORIZATION_HEADER); String authHeader = request.getHeader(AUTHORIZATION_HEADER);
if (authHeader != null && authHeader.startsWith(BEARER_PREFIX)) { if (authHeader != null && authHeader.startsWith(BEARER_PREFIX)) {
@ -189,7 +198,7 @@ public class JwtService implements JwtServiceInterface {
} }
@Override @Override
public void addTokenToResponse(HttpServletResponse response, String token) { public void addToken(HttpServletResponse response, String token) {
response.setHeader(AUTHORIZATION_HEADER, Newlines.stripAll(BEARER_PREFIX + token)); response.setHeader(AUTHORIZATION_HEADER, Newlines.stripAll(BEARER_PREFIX + token));
ResponseCookie cookie = ResponseCookie cookie =
@ -205,7 +214,7 @@ public class JwtService implements JwtServiceInterface {
} }
@Override @Override
public void clearTokenFromResponse(HttpServletResponse response) { public void clearToken(HttpServletResponse response) {
response.setHeader(AUTHORIZATION_HEADER, null); response.setHeader(AUTHORIZATION_HEADER, null);
ResponseCookie cookie = ResponseCookie cookie =
@ -225,17 +234,22 @@ public class JwtService implements JwtServiceInterface {
return v2Enabled; return v2Enabled;
} }
private String extractKeyIdFromToken(String token) { private String extractKeyId(String token) {
try { try {
return (String) PublicKey signingKey = keystoreService.getActiveKeyPair().getPublic();
Jwts.parser()
.unsecured() String keyId =
.build() (String)
.parseUnsecuredClaims(token) Jwts.parser()
.getHeader() .verifyWith(signingKey)
.get("kid"); .build()
.parse(token)
.getHeader()
.get("kid");
log.debug("Extracted key ID from token: {}", keyId);
return keyId;
} catch (Exception e) { } catch (Exception e) {
log.debug("Failed to extract key ID from token header: {}", e.getMessage()); log.warn("Failed to extract key ID from token header: {}", e.getMessage());
return null; return null;
} }
} }

View File

@ -48,7 +48,7 @@ public interface JwtServiceInterface {
* @param token the JWT token * @param token the JWT token
* @return map of claims * @return map of claims
*/ */
Map<String, Object> extractAllClaims(String token); Map<String, Object> extractClaims(String token);
/** /**
* Check if token is expired * Check if token is expired
@ -64,7 +64,7 @@ public interface JwtServiceInterface {
* @param request HTTP servlet request * @param request HTTP servlet request
* @return JWT token if found, null otherwise * @return JWT token if found, null otherwise
*/ */
String extractTokenFromRequest(HttpServletRequest request); String extractToken(HttpServletRequest request);
/** /**
* Add JWT token to HTTP response (header and cookie) * Add JWT token to HTTP response (header and cookie)
@ -72,14 +72,14 @@ public interface JwtServiceInterface {
* @param response HTTP servlet response * @param response HTTP servlet response
* @param token JWT token to add * @param token JWT token to add
*/ */
void addTokenToResponse(HttpServletResponse response, String token); void addToken(HttpServletResponse response, String token);
/** /**
* Clear JWT token from HTTP response (remove cookie) * Clear JWT token from HTTP response (remove cookie)
* *
* @param response HTTP servlet response * @param response HTTP servlet response
*/ */
void clearTokenFromResponse(HttpServletResponse response); void clearToken(HttpServletResponse response);
/** /**
* Check if JWT authentication is enabled * Check if JWT authentication is enabled

View File

@ -33,8 +33,8 @@ class CustomLogoutSuccessHandlerTest {
String logoutPath = "/login?logout=true"; String logoutPath = "/login?logout=true";
when(response.isCommitted()).thenReturn(false); when(response.isCommitted()).thenReturn(false);
when(jwtService.extractTokenFromRequest(request)).thenReturn(token); when(jwtService.extractToken(request)).thenReturn(token);
doNothing().when(jwtService).clearTokenFromResponse(response); doNothing().when(jwtService).clearToken(response);
when(request.getContextPath()).thenReturn(""); when(request.getContextPath()).thenReturn("");
when(response.encodeRedirectURL(logoutPath)).thenReturn(logoutPath); when(response.encodeRedirectURL(logoutPath)).thenReturn(logoutPath);
@ -51,15 +51,15 @@ class CustomLogoutSuccessHandlerTest {
String token = "token"; String token = "token";
when(response.isCommitted()).thenReturn(false); when(response.isCommitted()).thenReturn(false);
when(jwtService.extractTokenFromRequest(request)).thenReturn(token); when(jwtService.extractToken(request)).thenReturn(token);
doNothing().when(jwtService).clearTokenFromResponse(response); doNothing().when(jwtService).clearToken(response);
when(request.getContextPath()).thenReturn(""); when(request.getContextPath()).thenReturn("");
when(response.encodeRedirectURL(logoutPath)).thenReturn(logoutPath); when(response.encodeRedirectURL(logoutPath)).thenReturn(logoutPath);
customLogoutSuccessHandler.onLogoutSuccess(request, response, null); customLogoutSuccessHandler.onLogoutSuccess(request, response, null);
verify(response).sendRedirect(logoutPath); verify(response).sendRedirect(logoutPath);
verify(jwtService).clearTokenFromResponse(response); verify(jwtService).clearToken(response);
} }
@Test @Test

View File

@ -81,7 +81,7 @@ class JwtAuthenticationFilterTest {
jwtAuthenticationFilter.doFilterInternal(request, response, filterChain); jwtAuthenticationFilter.doFilterInternal(request, response, filterChain);
verify(filterChain).doFilter(request, response); verify(filterChain).doFilter(request, response);
verify(jwtService, never()).extractTokenFromRequest(any()); verify(jwtService, never()).extractToken(any());
} }
@Test @Test
@ -105,9 +105,9 @@ class JwtAuthenticationFilterTest {
when(jwtService.isJwtEnabled()).thenReturn(true); when(jwtService.isJwtEnabled()).thenReturn(true);
when(request.getContextPath()).thenReturn("/"); when(request.getContextPath()).thenReturn("/");
when(request.getRequestURI()).thenReturn("/protected"); when(request.getRequestURI()).thenReturn("/protected");
when(jwtService.extractTokenFromRequest(request)).thenReturn(token); when(jwtService.extractToken(request)).thenReturn(token);
doNothing().when(jwtService).validateToken(token); doNothing().when(jwtService).validateToken(token);
when(jwtService.extractAllClaims(token)).thenReturn(claims); when(jwtService.extractClaims(token)).thenReturn(claims);
when(userDetails.getAuthorities()).thenReturn(Collections.emptyList()); when(userDetails.getAuthorities()).thenReturn(Collections.emptyList());
when(userDetailsService.loadUserByUsername(username)).thenReturn(userDetails); when(userDetailsService.loadUserByUsername(username)).thenReturn(userDetails);
@ -122,11 +122,11 @@ class JwtAuthenticationFilterTest {
jwtAuthenticationFilter.doFilterInternal(request, response, filterChain); jwtAuthenticationFilter.doFilterInternal(request, response, filterChain);
verify(jwtService).validateToken(token); verify(jwtService).validateToken(token);
verify(jwtService).extractAllClaims(token); verify(jwtService).extractClaims(token);
verify(userDetailsService).loadUserByUsername(username); verify(userDetailsService).loadUserByUsername(username);
verify(securityContext).setAuthentication(any(UsernamePasswordAuthenticationToken.class)); verify(securityContext).setAuthentication(any(UsernamePasswordAuthenticationToken.class));
verify(jwtService).generateToken(any(UsernamePasswordAuthenticationToken.class), eq(claims)); verify(jwtService).generateToken(any(UsernamePasswordAuthenticationToken.class), eq(claims));
verify(jwtService).addTokenToResponse(response, newToken); verify(jwtService).addToken(response, newToken);
verify(filterChain).doFilter(request, response); verify(filterChain).doFilter(request, response);
} }
} }
@ -136,7 +136,7 @@ class JwtAuthenticationFilterTest {
when(jwtService.isJwtEnabled()).thenReturn(true); when(jwtService.isJwtEnabled()).thenReturn(true);
when(request.getRequestURI()).thenReturn("/"); when(request.getRequestURI()).thenReturn("/");
when(request.getMethod()).thenReturn("GET"); when(request.getMethod()).thenReturn("GET");
when(jwtService.extractTokenFromRequest(request)).thenReturn(null); when(jwtService.extractToken(request)).thenReturn(null);
jwtAuthenticationFilter.doFilterInternal(request, response, filterChain); jwtAuthenticationFilter.doFilterInternal(request, response, filterChain);
@ -151,7 +151,7 @@ class JwtAuthenticationFilterTest {
when(jwtService.isJwtEnabled()).thenReturn(true); when(jwtService.isJwtEnabled()).thenReturn(true);
when(request.getRequestURI()).thenReturn("/protected"); when(request.getRequestURI()).thenReturn("/protected");
when(request.getContextPath()).thenReturn("/"); when(request.getContextPath()).thenReturn("/");
when(jwtService.extractTokenFromRequest(request)).thenReturn(token); when(jwtService.extractToken(request)).thenReturn(token);
doThrow(new AuthenticationFailureException("Invalid token")).when(jwtService).validateToken(token); doThrow(new AuthenticationFailureException("Invalid token")).when(jwtService).validateToken(token);
jwtAuthenticationFilter.doFilterInternal(request, response, filterChain); jwtAuthenticationFilter.doFilterInternal(request, response, filterChain);
@ -168,7 +168,7 @@ class JwtAuthenticationFilterTest {
when(jwtService.isJwtEnabled()).thenReturn(true); when(jwtService.isJwtEnabled()).thenReturn(true);
when(request.getRequestURI()).thenReturn("/protected"); when(request.getRequestURI()).thenReturn("/protected");
when(request.getContextPath()).thenReturn("/"); when(request.getContextPath()).thenReturn("/");
when(jwtService.extractTokenFromRequest(request)).thenReturn(token); when(jwtService.extractToken(request)).thenReturn(token);
doThrow(new AuthenticationFailureException("The token has expired")).when(jwtService).validateToken(token); doThrow(new AuthenticationFailureException("The token has expired")).when(jwtService).validateToken(token);
jwtAuthenticationFilter.doFilterInternal(request, response, filterChain); jwtAuthenticationFilter.doFilterInternal(request, response, filterChain);
@ -187,9 +187,9 @@ class JwtAuthenticationFilterTest {
when(jwtService.isJwtEnabled()).thenReturn(true); when(jwtService.isJwtEnabled()).thenReturn(true);
when(request.getRequestURI()).thenReturn("/protected"); when(request.getRequestURI()).thenReturn("/protected");
when(request.getContextPath()).thenReturn("/"); when(request.getContextPath()).thenReturn("/");
when(jwtService.extractTokenFromRequest(request)).thenReturn(token); when(jwtService.extractToken(request)).thenReturn(token);
doNothing().when(jwtService).validateToken(token); doNothing().when(jwtService).validateToken(token);
when(jwtService.extractAllClaims(token)).thenReturn(claims); when(jwtService.extractClaims(token)).thenReturn(claims);
when(userDetailsService.loadUserByUsername(username)).thenReturn(null); when(userDetailsService.loadUserByUsername(username)).thenReturn(null);
try (MockedStatic<SecurityContextHolder> mockedSecurityContextHolder = mockStatic(SecurityContextHolder.class)) { try (MockedStatic<SecurityContextHolder> mockedSecurityContextHolder = mockStatic(SecurityContextHolder.class)) {
@ -209,7 +209,7 @@ class JwtAuthenticationFilterTest {
when(jwtService.isJwtEnabled()).thenReturn(true); when(jwtService.isJwtEnabled()).thenReturn(true);
when(request.getRequestURI()).thenReturn("/protected"); when(request.getRequestURI()).thenReturn("/protected");
when(request.getContextPath()).thenReturn("/"); when(request.getContextPath()).thenReturn("/");
when(jwtService.extractTokenFromRequest(request)).thenReturn(null); when(jwtService.extractToken(request)).thenReturn(null);
jwtAuthenticationFilter.doFilterInternal(request, response, filterChain); jwtAuthenticationFilter.doFilterInternal(request, response, filterChain);

View File

@ -7,8 +7,6 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.NullAndEmptySource; import org.junit.jupiter.params.provider.NullAndEmptySource;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletRequest;
@ -105,7 +103,7 @@ class JwtSaml2AuthenticationRequestRepositoryTest {
); );
when(request.getParameter("RelayState")).thenReturn(relayState); when(request.getParameter("RelayState")).thenReturn(relayState);
when(jwtService.extractAllClaims(token)).thenReturn(claims); when(jwtService.extractClaims(token)).thenReturn(claims);
when(relyingPartyRegistrationRepository.findByRegistrationId("stirling-pdf")).thenReturn(relyingPartyRegistration); when(relyingPartyRegistrationRepository.findByRegistrationId("stirling-pdf")).thenReturn(relyingPartyRegistration);
when(relyingPartyRegistration.getRegistrationId()).thenReturn("stirling-pdf"); when(relyingPartyRegistration.getRegistrationId()).thenReturn("stirling-pdf");
when(relyingPartyRegistration.getAssertingPartyMetadata()).thenReturn(assertingPartyMetadata); when(relyingPartyRegistration.getAssertingPartyMetadata()).thenReturn(assertingPartyMetadata);
@ -153,7 +151,7 @@ class JwtSaml2AuthenticationRequestRepositoryTest {
); );
when(request.getParameter("RelayState")).thenReturn(relayState); when(request.getParameter("RelayState")).thenReturn(relayState);
when(jwtService.extractAllClaims(token)).thenReturn(claims); when(jwtService.extractClaims(token)).thenReturn(claims);
when(relyingPartyRegistrationRepository.findByRegistrationId("stirling-pdf")).thenReturn(null); when(relyingPartyRegistrationRepository.findByRegistrationId("stirling-pdf")).thenReturn(null);
tokenStore.put(relayState, token); tokenStore.put(relayState, token);
@ -179,7 +177,7 @@ class JwtSaml2AuthenticationRequestRepositoryTest {
); );
when(request.getParameter("RelayState")).thenReturn(relayState); when(request.getParameter("RelayState")).thenReturn(relayState);
when(jwtService.extractAllClaims(token)).thenReturn(claims); when(jwtService.extractClaims(token)).thenReturn(claims);
when(relyingPartyRegistrationRepository.findByRegistrationId("stirling-pdf")).thenReturn(relyingPartyRegistration); when(relyingPartyRegistrationRepository.findByRegistrationId("stirling-pdf")).thenReturn(relyingPartyRegistration);
when(relyingPartyRegistration.getRegistrationId()).thenReturn("stirling-pdf"); when(relyingPartyRegistration.getRegistrationId()).thenReturn("stirling-pdf");
when(relyingPartyRegistration.getAssertingPartyMetadata()).thenReturn(assertingPartyMetadata); when(relyingPartyRegistration.getAssertingPartyMetadata()).thenReturn(assertingPartyMetadata);

View File

@ -70,7 +70,7 @@ class JwtKeyCleanupServiceTest {
cleanupService.cleanup(); cleanupService.cleanup();
verify(signingKeyRepository, never()).countKeysEligibleForCleanup(any(LocalDateTime.class)); verify(signingKeyRepository, never()).countKeysEligibleForCleanup(any(LocalDateTime.class));
verify(signingKeyRepository, never()).findInactiveKeysOlderThan(any(LocalDateTime.class), any(Pageable.class)); verify(signingKeyRepository, never()).findKeysOlderThan(any(LocalDateTime.class), any(Pageable.class));
} }
@Test @Test
@ -80,7 +80,7 @@ class JwtKeyCleanupServiceTest {
cleanupService.cleanup(); cleanupService.cleanup();
verify(signingKeyRepository, never()).countKeysEligibleForCleanup(any(LocalDateTime.class)); verify(signingKeyRepository, never()).countKeysEligibleForCleanup(any(LocalDateTime.class));
verify(signingKeyRepository, never()).findInactiveKeysOlderThan(any(LocalDateTime.class), any(Pageable.class)); verify(signingKeyRepository, never()).findKeysOlderThan(any(LocalDateTime.class), any(Pageable.class));
} }
@Test @Test
@ -90,7 +90,7 @@ class JwtKeyCleanupServiceTest {
cleanupService.cleanup(); cleanupService.cleanup();
verify(signingKeyRepository).countKeysEligibleForCleanup(any(LocalDateTime.class)); verify(signingKeyRepository).countKeysEligibleForCleanup(any(LocalDateTime.class));
verify(signingKeyRepository, never()).findInactiveKeysOlderThan(any(LocalDateTime.class), any(Pageable.class)); verify(signingKeyRepository, never()).findKeysOlderThan(any(LocalDateTime.class), any(Pageable.class));
} }
@Test @Test
@ -106,14 +106,14 @@ class JwtKeyCleanupServiceTest {
createTestKeyFile("key-2"); createTestKeyFile("key-2");
when(signingKeyRepository.countKeysEligibleForCleanup(any(LocalDateTime.class))).thenReturn(2L); when(signingKeyRepository.countKeysEligibleForCleanup(any(LocalDateTime.class))).thenReturn(2L);
when(signingKeyRepository.findInactiveKeysOlderThan(any(LocalDateTime.class), any(Pageable.class))) when(signingKeyRepository.findKeysOlderThan(any(LocalDateTime.class), any(Pageable.class)))
.thenReturn(keysToCleanup) .thenReturn(keysToCleanup)
.thenReturn(Collections.emptyList()); .thenReturn(Collections.emptyList());
cleanupService.cleanup(); cleanupService.cleanup();
verify(signingKeyRepository).countKeysEligibleForCleanup(any(LocalDateTime.class)); verify(signingKeyRepository).countKeysEligibleForCleanup(any(LocalDateTime.class));
verify(signingKeyRepository).findInactiveKeysOlderThan(any(LocalDateTime.class), any(Pageable.class)); verify(signingKeyRepository).findKeysOlderThan(any(LocalDateTime.class), any(Pageable.class));
verify(signingKeyRepository).deleteAllByIdInBatch(Arrays.asList(1L, 2L)); verify(signingKeyRepository).deleteAllByIdInBatch(Arrays.asList(1L, 2L));
assertFalse(Files.exists(tempDir.resolve("key-1.key"))); assertFalse(Files.exists(tempDir.resolve("key-1.key")));
@ -140,7 +140,7 @@ class JwtKeyCleanupServiceTest {
createTestKeyFile("key-3"); createTestKeyFile("key-3");
when(signingKeyRepository.countKeysEligibleForCleanup(any(LocalDateTime.class))).thenReturn(3L); when(signingKeyRepository.countKeysEligibleForCleanup(any(LocalDateTime.class))).thenReturn(3L);
when(signingKeyRepository.findInactiveKeysOlderThan(any(LocalDateTime.class), any(Pageable.class))) when(signingKeyRepository.findKeysOlderThan(any(LocalDateTime.class), any(Pageable.class)))
.thenReturn(firstBatch) .thenReturn(firstBatch)
.thenReturn(secondBatch) .thenReturn(secondBatch)
.thenReturn(Collections.emptyList()); .thenReturn(Collections.emptyList());
@ -165,7 +165,7 @@ class JwtKeyCleanupServiceTest {
createTestKeyFile("key-1"); createTestKeyFile("key-1");
when(signingKeyRepository.countKeysEligibleForCleanup(any(LocalDateTime.class))).thenReturn(2L); when(signingKeyRepository.countKeysEligibleForCleanup(any(LocalDateTime.class))).thenReturn(2L);
when(signingKeyRepository.findInactiveKeysOlderThan(any(LocalDateTime.class), any(Pageable.class))) when(signingKeyRepository.findKeysOlderThan(any(LocalDateTime.class), any(Pageable.class)))
.thenReturn(keysToCleanup) .thenReturn(keysToCleanup)
.thenReturn(Collections.emptyList()); .thenReturn(Collections.emptyList());
@ -226,7 +226,7 @@ class JwtKeyCleanupServiceTest {
cleanupService.cleanup(); cleanupService.cleanup();
verify(signingKeyRepository, never()).countKeysEligibleForCleanup(any(LocalDateTime.class)); verify(signingKeyRepository, never()).countKeysEligibleForCleanup(any(LocalDateTime.class));
verify(signingKeyRepository, never()).findInactiveKeysOlderThan(any(LocalDateTime.class), any(Pageable.class)); verify(signingKeyRepository, never()).findKeysOlderThan(any(LocalDateTime.class), any(Pageable.class));
verify(signingKeyRepository, never()).deleteAllByIdInBatch(any()); verify(signingKeyRepository, never()).deleteAllByIdInBatch(any());
} }

View File

@ -77,14 +77,14 @@ class JwtKeystoreServiceInterfaceTest {
} }
@Test @Test
void testGetActiveKeypairWhenKeystoreDisabled() { void testGetActiveKeyPairWhenKeystoreDisabled() {
when(jwtConfig.isEnableKeystore()).thenReturn(false); when(jwtConfig.isEnableKeystore()).thenReturn(false);
try (MockedStatic<InstallationPathConfig> mockedStatic = mockStatic(InstallationPathConfig.class)) { try (MockedStatic<InstallationPathConfig> mockedStatic = mockStatic(InstallationPathConfig.class)) {
mockedStatic.when(InstallationPathConfig::getPrivateKeyPath).thenReturn(tempDir.toString()); mockedStatic.when(InstallationPathConfig::getPrivateKeyPath).thenReturn(tempDir.toString());
keystoreService = new JwtKeystoreService(repository, applicationProperties); keystoreService = new JwtKeystoreService(repository, applicationProperties);
KeyPair result = keystoreService.getActiveKeypair(); KeyPair result = keystoreService.getActiveKeyPair();
assertNotNull(result); assertNotNull(result);
assertNotNull(result.getPublic()); assertNotNull(result.getPublic());
@ -94,14 +94,14 @@ class JwtKeystoreServiceInterfaceTest {
@Test @Test
void testGetActiveKeypairWhenNoActiveKeyExists() { void testGetActiveKeypairWhenNoActiveKeyExists() {
when(repository.findByIsActiveTrue()).thenReturn(Optional.empty()); when(repository.findFirstByIsActiveTrueOrderByCreatedAtDesc()).thenReturn(Optional.empty());
try (MockedStatic<InstallationPathConfig> mockedStatic = mockStatic(InstallationPathConfig.class)) { try (MockedStatic<InstallationPathConfig> mockedStatic = mockStatic(InstallationPathConfig.class)) {
mockedStatic.when(InstallationPathConfig::getPrivateKeyPath).thenReturn(tempDir.toString()); mockedStatic.when(InstallationPathConfig::getPrivateKeyPath).thenReturn(tempDir.toString());
keystoreService = new JwtKeystoreService(repository, applicationProperties); keystoreService = new JwtKeystoreService(repository, applicationProperties);
keystoreService.initializeKeystore(); keystoreService.initializeKeystore();
KeyPair result = keystoreService.getActiveKeypair(); KeyPair result = keystoreService.getActiveKeyPair();
assertNotNull(result); assertNotNull(result);
verify(repository).save(any(JwtSigningKey.class)); verify(repository).save(any(JwtSigningKey.class));
@ -109,13 +109,13 @@ class JwtKeystoreServiceInterfaceTest {
} }
@Test @Test
void testGetActiveKeypairWithExistingKey() throws Exception { void testGetActiveKeyPairWithExistingKey() throws Exception {
String keyId = "test-key-2024-01-01-120000"; String keyId = "test-key-2024-01-01-120000";
String publicKeyBase64 = Base64.getEncoder().encodeToString(testKeyPair.getPublic().getEncoded()); String publicKeyBase64 = Base64.getEncoder().encodeToString(testKeyPair.getPublic().getEncoded());
String privateKeyBase64 = Base64.getEncoder().encodeToString(testKeyPair.getPrivate().getEncoded()); String privateKeyBase64 = Base64.getEncoder().encodeToString(testKeyPair.getPrivate().getEncoded());
JwtSigningKey existingKey = new JwtSigningKey(keyId, publicKeyBase64, "RS256"); JwtSigningKey existingKey = new JwtSigningKey(keyId, publicKeyBase64, "RS256");
when(repository.findByIsActiveTrue()).thenReturn(Optional.of(existingKey)); when(repository.findFirstByIsActiveTrueOrderByCreatedAtDesc()).thenReturn(Optional.of(existingKey));
Path keyFile = tempDir.resolve(keyId + ".key"); Path keyFile = tempDir.resolve(keyId + ".key");
Files.writeString(keyFile, privateKeyBase64); Files.writeString(keyFile, privateKeyBase64);
@ -125,7 +125,7 @@ class JwtKeystoreServiceInterfaceTest {
keystoreService = new JwtKeystoreService(repository, applicationProperties); keystoreService = new JwtKeystoreService(repository, applicationProperties);
keystoreService.initializeKeystore(); keystoreService.initializeKeystore();
KeyPair result = keystoreService.getActiveKeypair(); KeyPair result = keystoreService.getActiveKeyPair();
assertNotNull(result); assertNotNull(result);
assertEquals(keyId, keystoreService.getActiveKeyId()); assertEquals(keyId, keystoreService.getActiveKeyId());
@ -133,7 +133,7 @@ class JwtKeystoreServiceInterfaceTest {
} }
@Test @Test
void testGetKeypairByKeyId() throws Exception { void testGetKeyPairByKeyId() throws Exception {
String keyId = "test-key-123"; String keyId = "test-key-123";
String publicKeyBase64 = Base64.getEncoder().encodeToString(testKeyPair.getPublic().getEncoded()); String publicKeyBase64 = Base64.getEncoder().encodeToString(testKeyPair.getPublic().getEncoded());
String privateKeyBase64 = Base64.getEncoder().encodeToString(testKeyPair.getPrivate().getEncoded()); String privateKeyBase64 = Base64.getEncoder().encodeToString(testKeyPair.getPrivate().getEncoded());
@ -148,7 +148,7 @@ class JwtKeystoreServiceInterfaceTest {
mockedStatic.when(InstallationPathConfig::getPrivateKeyPath).thenReturn(tempDir.toString()); mockedStatic.when(InstallationPathConfig::getPrivateKeyPath).thenReturn(tempDir.toString());
keystoreService = new JwtKeystoreService(repository, applicationProperties); keystoreService = new JwtKeystoreService(repository, applicationProperties);
Optional<KeyPair> result = keystoreService.getKeypairByKeyId(keyId); Optional<KeyPair> result = keystoreService.getKeyPairByKeyId(keyId);
assertTrue(result.isPresent()); assertTrue(result.isPresent());
assertNotNull(result.get().getPublic()); assertNotNull(result.get().getPublic());
@ -157,7 +157,7 @@ class JwtKeystoreServiceInterfaceTest {
} }
@Test @Test
void testGetKeypairByKeyIdNotFound() { void testGetKeyPairByKeyIdNotFound() {
String keyId = "non-existent-key"; String keyId = "non-existent-key";
when(repository.findByKeyId(keyId)).thenReturn(Optional.empty()); when(repository.findByKeyId(keyId)).thenReturn(Optional.empty());
@ -165,21 +165,21 @@ class JwtKeystoreServiceInterfaceTest {
mockedStatic.when(InstallationPathConfig::getPrivateKeyPath).thenReturn(tempDir.toString()); mockedStatic.when(InstallationPathConfig::getPrivateKeyPath).thenReturn(tempDir.toString());
keystoreService = new JwtKeystoreService(repository, applicationProperties); keystoreService = new JwtKeystoreService(repository, applicationProperties);
Optional<KeyPair> result = keystoreService.getKeypairByKeyId(keyId); Optional<KeyPair> result = keystoreService.getKeyPairByKeyId(keyId);
assertFalse(result.isPresent()); assertFalse(result.isPresent());
} }
} }
@Test @Test
void testGetKeypairByKeyIdWhenKeystoreDisabled() { void testGetKeyPairByKeyIdWhenKeystoreDisabled() {
when(jwtConfig.isEnableKeystore()).thenReturn(false); when(jwtConfig.isEnableKeystore()).thenReturn(false);
try (MockedStatic<InstallationPathConfig> mockedStatic = mockStatic(InstallationPathConfig.class)) { try (MockedStatic<InstallationPathConfig> mockedStatic = mockStatic(InstallationPathConfig.class)) {
mockedStatic.when(InstallationPathConfig::getPrivateKeyPath).thenReturn(tempDir.toString()); mockedStatic.when(InstallationPathConfig::getPrivateKeyPath).thenReturn(tempDir.toString());
keystoreService = new JwtKeystoreService(repository, applicationProperties); keystoreService = new JwtKeystoreService(repository, applicationProperties);
Optional<KeyPair> result = keystoreService.getKeypairByKeyId("any-key"); Optional<KeyPair> result = keystoreService.getKeyPairByKeyId("any-key");
assertFalse(result.isPresent()); assertFalse(result.isPresent());
} }
@ -187,7 +187,7 @@ class JwtKeystoreServiceInterfaceTest {
@Test @Test
void testInitializeKeystoreCreatesDirectory() throws IOException { void testInitializeKeystoreCreatesDirectory() throws IOException {
when(repository.findByIsActiveTrue()).thenReturn(Optional.empty()); when(repository.findFirstByIsActiveTrueOrderByCreatedAtDesc()).thenReturn(Optional.empty());
try (MockedStatic<InstallationPathConfig> mockedStatic = mockStatic(InstallationPathConfig.class)) { try (MockedStatic<InstallationPathConfig> mockedStatic = mockStatic(InstallationPathConfig.class)) {
mockedStatic.when(InstallationPathConfig::getPrivateKeyPath).thenReturn(tempDir.toString()); mockedStatic.when(InstallationPathConfig::getPrivateKeyPath).thenReturn(tempDir.toString());
@ -205,14 +205,14 @@ class JwtKeystoreServiceInterfaceTest {
String publicKeyBase64 = Base64.getEncoder().encodeToString(testKeyPair.getPublic().getEncoded()); String publicKeyBase64 = Base64.getEncoder().encodeToString(testKeyPair.getPublic().getEncoded());
JwtSigningKey existingKey = new JwtSigningKey(keyId, publicKeyBase64, "RS256"); JwtSigningKey existingKey = new JwtSigningKey(keyId, publicKeyBase64, "RS256");
when(repository.findByIsActiveTrue()).thenReturn(Optional.of(existingKey)); when(repository.findFirstByIsActiveTrueOrderByCreatedAtDesc()).thenReturn(Optional.of(existingKey));
try (MockedStatic<InstallationPathConfig> mockedStatic = mockStatic(InstallationPathConfig.class)) { try (MockedStatic<InstallationPathConfig> mockedStatic = mockStatic(InstallationPathConfig.class)) {
mockedStatic.when(InstallationPathConfig::getPrivateKeyPath).thenReturn(tempDir.toString()); mockedStatic.when(InstallationPathConfig::getPrivateKeyPath).thenReturn(tempDir.toString());
keystoreService = new JwtKeystoreService(repository, applicationProperties); keystoreService = new JwtKeystoreService(repository, applicationProperties);
keystoreService.initializeKeystore(); keystoreService.initializeKeystore();
KeyPair result = keystoreService.getActiveKeypair(); KeyPair result = keystoreService.getActiveKeyPair();
assertNotNull(result); assertNotNull(result);
verify(repository).save(any(JwtSigningKey.class)); verify(repository).save(any(JwtSigningKey.class));

View File

@ -74,7 +74,7 @@ class JwtServiceTest {
void testGenerateTokenWithAuthentication() { void testGenerateTokenWithAuthentication() {
String username = "testuser"; String username = "testuser";
when(keystoreService.getActiveKeypair()).thenReturn(testKeyPair); when(keystoreService.getActiveKeyPair()).thenReturn(testKeyPair);
when(keystoreService.getActiveKeyId()).thenReturn("test-key-id"); when(keystoreService.getActiveKeyId()).thenReturn("test-key-id");
when(authentication.getPrincipal()).thenReturn(userDetails); when(authentication.getPrincipal()).thenReturn(userDetails);
when(userDetails.getUsername()).thenReturn(username); when(userDetails.getUsername()).thenReturn(username);
@ -93,7 +93,7 @@ class JwtServiceTest {
claims.put("role", "admin"); claims.put("role", "admin");
claims.put("department", "IT"); claims.put("department", "IT");
when(keystoreService.getActiveKeypair()).thenReturn(testKeyPair); when(keystoreService.getActiveKeyPair()).thenReturn(testKeyPair);
when(keystoreService.getActiveKeyId()).thenReturn("test-key-id"); when(keystoreService.getActiveKeyId()).thenReturn("test-key-id");
when(authentication.getPrincipal()).thenReturn(userDetails); when(authentication.getPrincipal()).thenReturn(userDetails);
when(userDetails.getUsername()).thenReturn(username); when(userDetails.getUsername()).thenReturn(username);
@ -104,14 +104,14 @@ class JwtServiceTest {
assertFalse(token.isEmpty()); assertFalse(token.isEmpty());
assertEquals(username, jwtService.extractUsername(token)); assertEquals(username, jwtService.extractUsername(token));
Map<String, Object> extractedClaims = jwtService.extractAllClaims(token); Map<String, Object> extractedClaims = jwtService.extractClaims(token);
assertEquals("admin", extractedClaims.get("role")); assertEquals("admin", extractedClaims.get("role"));
assertEquals("IT", extractedClaims.get("department")); assertEquals("IT", extractedClaims.get("department"));
} }
@Test @Test
void testValidateTokenSuccess() { void testValidateTokenSuccess() {
when(keystoreService.getActiveKeypair()).thenReturn(testKeyPair); when(keystoreService.getActiveKeyPair()).thenReturn(testKeyPair);
when(keystoreService.getActiveKeyId()).thenReturn("test-key-id"); when(keystoreService.getActiveKeyId()).thenReturn("test-key-id");
when(authentication.getPrincipal()).thenReturn(userDetails); when(authentication.getPrincipal()).thenReturn(userDetails);
when(userDetails.getUsername()).thenReturn("testuser"); when(userDetails.getUsername()).thenReturn("testuser");
@ -123,7 +123,7 @@ class JwtServiceTest {
@Test @Test
void testValidateTokenWithInvalidToken() { void testValidateTokenWithInvalidToken() {
when(keystoreService.getActiveKeypair()).thenReturn(testKeyPair); when(keystoreService.getActiveKeyPair()).thenReturn(testKeyPair);
assertThrows(AuthenticationFailureException.class, () -> { assertThrows(AuthenticationFailureException.class, () -> {
jwtService.validateToken("invalid-token"); jwtService.validateToken("invalid-token");
@ -132,7 +132,7 @@ class JwtServiceTest {
@Test @Test
void testValidateTokenWithMalformedToken() { void testValidateTokenWithMalformedToken() {
when(keystoreService.getActiveKeypair()).thenReturn(testKeyPair); when(keystoreService.getActiveKeyPair()).thenReturn(testKeyPair);
AuthenticationFailureException exception = assertThrows(AuthenticationFailureException.class, () -> { AuthenticationFailureException exception = assertThrows(AuthenticationFailureException.class, () -> {
jwtService.validateToken("malformed.token"); jwtService.validateToken("malformed.token");
@ -143,7 +143,7 @@ class JwtServiceTest {
@Test @Test
void testValidateTokenWithEmptyToken() { void testValidateTokenWithEmptyToken() {
when(keystoreService.getActiveKeypair()).thenReturn(testKeyPair); when(keystoreService.getActiveKeyPair()).thenReturn(testKeyPair);
AuthenticationFailureException exception = assertThrows(AuthenticationFailureException.class, () -> { AuthenticationFailureException exception = assertThrows(AuthenticationFailureException.class, () -> {
jwtService.validateToken(""); jwtService.validateToken("");
@ -158,7 +158,7 @@ class JwtServiceTest {
User user = mock(User.class); User user = mock(User.class);
Map<String, Object> claims = Map.of("sub", "testuser", "authType", "WEB"); Map<String, Object> claims = Map.of("sub", "testuser", "authType", "WEB");
when(keystoreService.getActiveKeypair()).thenReturn(testKeyPair); when(keystoreService.getActiveKeyPair()).thenReturn(testKeyPair);
when(keystoreService.getActiveKeyId()).thenReturn("test-key-id"); when(keystoreService.getActiveKeyId()).thenReturn("test-key-id");
when(authentication.getPrincipal()).thenReturn(user); when(authentication.getPrincipal()).thenReturn(user);
when(user.getUsername()).thenReturn(username); when(user.getUsername()).thenReturn(username);
@ -170,23 +170,23 @@ class JwtServiceTest {
@Test @Test
void testExtractUsernameWithInvalidToken() { void testExtractUsernameWithInvalidToken() {
when(keystoreService.getActiveKeypair()).thenReturn(testKeyPair); when(keystoreService.getActiveKeyPair()).thenReturn(testKeyPair);
assertThrows(AuthenticationFailureException.class, () -> jwtService.extractUsername("invalid-token")); assertThrows(AuthenticationFailureException.class, () -> jwtService.extractUsername("invalid-token"));
} }
@Test @Test
void testExtractAllClaims() { void testExtractClaims() {
String username = "testuser"; String username = "testuser";
Map<String, Object> claims = Map.of("role", "admin", "department", "IT"); Map<String, Object> claims = Map.of("role", "admin", "department", "IT");
when(keystoreService.getActiveKeypair()).thenReturn(testKeyPair); when(keystoreService.getActiveKeyPair()).thenReturn(testKeyPair);
when(keystoreService.getActiveKeyId()).thenReturn("test-key-id"); when(keystoreService.getActiveKeyId()).thenReturn("test-key-id");
when(authentication.getPrincipal()).thenReturn(userDetails); when(authentication.getPrincipal()).thenReturn(userDetails);
when(userDetails.getUsername()).thenReturn(username); when(userDetails.getUsername()).thenReturn(username);
String token = jwtService.generateToken(authentication, claims); String token = jwtService.generateToken(authentication, claims);
Map<String, Object> extractedClaims = jwtService.extractAllClaims(token); Map<String, Object> extractedClaims = jwtService.extractClaims(token);
assertEquals("admin", extractedClaims.get("role")); assertEquals("admin", extractedClaims.get("role"));
assertEquals("IT", extractedClaims.get("department")); assertEquals("IT", extractedClaims.get("department"));
@ -195,60 +195,60 @@ class JwtServiceTest {
} }
@Test @Test
void testExtractAllClaimsWithInvalidToken() { void testExtractClaimsWithInvalidToken() {
when(keystoreService.getActiveKeypair()).thenReturn(testKeyPair); when(keystoreService.getActiveKeyPair()).thenReturn(testKeyPair);
assertThrows(AuthenticationFailureException.class, () -> jwtService.extractAllClaims("invalid-token")); assertThrows(AuthenticationFailureException.class, () -> jwtService.extractClaims("invalid-token"));
} }
@Test @Test
void testExtractTokenFromRequestWithAuthorizationHeader() { void testExtractTokenWithAuthorizationHeader() {
String token = "test-token"; String token = "test-token";
when(request.getHeader("Authorization")).thenReturn("Bearer " + token); when(request.getHeader("Authorization")).thenReturn("Bearer " + token);
assertEquals(token, jwtService.extractTokenFromRequest(request)); assertEquals(token, jwtService.extractToken(request));
} }
@Test @Test
void testExtractTokenFromRequestWithCookie() { void testExtractTokenWithCookie() {
String token = "test-token"; String token = "test-token";
Cookie[] cookies = { new Cookie("stirling_jwt", token) }; Cookie[] cookies = { new Cookie("stirling_jwt", token) };
when(request.getHeader("Authorization")).thenReturn(null); when(request.getHeader("Authorization")).thenReturn(null);
when(request.getCookies()).thenReturn(cookies); when(request.getCookies()).thenReturn(cookies);
assertEquals(token, jwtService.extractTokenFromRequest(request)); assertEquals(token, jwtService.extractToken(request));
} }
@Test @Test
void testExtractTokenFromRequestWithNoCookies() { void testExtractTokenWithNoCookies() {
when(request.getHeader("Authorization")).thenReturn(null); when(request.getHeader("Authorization")).thenReturn(null);
when(request.getCookies()).thenReturn(null); when(request.getCookies()).thenReturn(null);
assertNull(jwtService.extractTokenFromRequest(request)); assertNull(jwtService.extractToken(request));
} }
@Test @Test
void testExtractTokenFromRequestWithWrongCookie() { void testExtractTokenWithWrongCookie() {
Cookie[] cookies = {new Cookie("OTHER_COOKIE", "value")}; Cookie[] cookies = {new Cookie("OTHER_COOKIE", "value")};
when(request.getHeader("Authorization")).thenReturn(null); when(request.getHeader("Authorization")).thenReturn(null);
when(request.getCookies()).thenReturn(cookies); when(request.getCookies()).thenReturn(cookies);
assertNull(jwtService.extractTokenFromRequest(request)); assertNull(jwtService.extractToken(request));
} }
@Test @Test
void testExtractTokenFromRequestWithInvalidAuthorizationHeader() { void testExtractTokenWithInvalidAuthorizationHeader() {
when(request.getHeader("Authorization")).thenReturn("Basic token"); when(request.getHeader("Authorization")).thenReturn("Basic token");
when(request.getCookies()).thenReturn(null); when(request.getCookies()).thenReturn(null);
assertNull(jwtService.extractTokenFromRequest(request)); assertNull(jwtService.extractToken(request));
} }
@Test @Test
void testAddTokenToResponse() { void testAddToken() {
String token = "test-token"; String token = "test-token";
jwtService.addTokenToResponse(response, token); jwtService.addToken(response, token);
verify(response).setHeader("Authorization", "Bearer " + token); verify(response).setHeader("Authorization", "Bearer " + token);
verify(response).addHeader(eq("Set-Cookie"), contains("stirling_jwt=" + token)); verify(response).addHeader(eq("Set-Cookie"), contains("stirling_jwt=" + token));
@ -257,8 +257,8 @@ class JwtServiceTest {
} }
@Test @Test
void testClearTokenFromResponse() { void testClearToken() {
jwtService.clearTokenFromResponse(response); jwtService.clearToken(response);
verify(response).setHeader("Authorization", null); verify(response).setHeader("Authorization", null);
verify(response).addHeader(eq("Set-Cookie"), contains("stirling_jwt=")); verify(response).addHeader(eq("Set-Cookie"), contains("stirling_jwt="));
@ -270,7 +270,7 @@ class JwtServiceTest {
String username = "testuser"; String username = "testuser";
Map<String, Object> claims = new HashMap<>(); Map<String, Object> claims = new HashMap<>();
when(keystoreService.getActiveKeypair()).thenReturn(testKeyPair); when(keystoreService.getActiveKeyPair()).thenReturn(testKeyPair);
when(keystoreService.getActiveKeyId()).thenReturn("test-key-id"); when(keystoreService.getActiveKeyId()).thenReturn("test-key-id");
when(authentication.getPrincipal()).thenReturn(userDetails); when(authentication.getPrincipal()).thenReturn(userDetails);
when(userDetails.getUsername()).thenReturn(username); when(userDetails.getUsername()).thenReturn(username);
@ -280,7 +280,7 @@ class JwtServiceTest {
assertNotNull(token); assertNotNull(token);
assertFalse(token.isEmpty()); assertFalse(token.isEmpty());
// Verify that the keystore service was called // Verify that the keystore service was called
verify(keystoreService).getActiveKeypair(); verify(keystoreService).getActiveKeyPair();
verify(keystoreService).getActiveKeyId(); verify(keystoreService).getActiveKeyId();
} }
@ -289,7 +289,7 @@ class JwtServiceTest {
String username = "testuser"; String username = "testuser";
Map<String, Object> claims = new HashMap<>(); Map<String, Object> claims = new HashMap<>();
when(keystoreService.getActiveKeypair()).thenReturn(testKeyPair); when(keystoreService.getActiveKeyPair()).thenReturn(testKeyPair);
when(keystoreService.getActiveKeyId()).thenReturn("test-key-id"); when(keystoreService.getActiveKeyId()).thenReturn("test-key-id");
when(authentication.getPrincipal()).thenReturn(userDetails); when(authentication.getPrincipal()).thenReturn(userDetails);
when(userDetails.getUsername()).thenReturn(username); when(userDetails.getUsername()).thenReturn(username);
@ -298,7 +298,7 @@ class JwtServiceTest {
String token = jwtService.generateToken(authentication, claims); String token = jwtService.generateToken(authentication, claims);
// Mock extraction of key ID and verification (lenient to avoid unused stubbing) // Mock extraction of key ID and verification (lenient to avoid unused stubbing)
lenient().when(keystoreService.getKeypairByKeyId("test-key-id")).thenReturn(Optional.of(testKeyPair)); lenient().when(keystoreService.getKeyPairByKeyId("test-key-id")).thenReturn(Optional.of(testKeyPair));
// Verify token can be validated // Verify token can be validated
assertDoesNotThrow(() -> jwtService.validateToken(token)); assertDoesNotThrow(() -> jwtService.validateToken(token));
@ -310,7 +310,7 @@ class JwtServiceTest {
String username = "testuser"; String username = "testuser";
Map<String, Object> claims = new HashMap<>(); Map<String, Object> claims = new HashMap<>();
when(keystoreService.getActiveKeypair()).thenReturn(testKeyPair); when(keystoreService.getActiveKeyPair()).thenReturn(testKeyPair);
when(keystoreService.getActiveKeyId()).thenReturn("test-key-id"); when(keystoreService.getActiveKeyId()).thenReturn("test-key-id");
when(authentication.getPrincipal()).thenReturn(userDetails); when(authentication.getPrincipal()).thenReturn(userDetails);
when(userDetails.getUsername()).thenReturn(username); when(userDetails.getUsername()).thenReturn(username);
@ -318,13 +318,13 @@ class JwtServiceTest {
String token = jwtService.generateToken(authentication, claims); String token = jwtService.generateToken(authentication, claims);
// Mock scenario where specific key ID is not found (lenient to avoid unused stubbing) // Mock scenario where specific key ID is not found (lenient to avoid unused stubbing)
lenient().when(keystoreService.getKeypairByKeyId("test-key-id")).thenReturn(Optional.empty()); lenient().when(keystoreService.getKeyPairByKeyId("test-key-id")).thenReturn(Optional.empty());
// Should still work using active keypair // Should still work using active keypair
assertDoesNotThrow(() -> jwtService.validateToken(token)); assertDoesNotThrow(() -> jwtService.validateToken(token));
assertEquals(username, jwtService.extractUsername(token)); assertEquals(username, jwtService.extractUsername(token));
// Verify fallback to active keypair was used (called multiple times during token operations) // Verify fallback to active keypair was used (called multiple times during token operations)
verify(keystoreService, atLeast(1)).getActiveKeypair(); verify(keystoreService, atLeast(1)).getActiveKeyPair();
} }
} }