mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-02 18:45:21 +00:00
Deactivating keys
This commit is contained in:
parent
72d0339588
commit
9d162abc8c
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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(
|
||||||
|
@ -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()
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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"));
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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));
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user