wip - refactoring

This commit is contained in:
Dario Ghunney Ware 2025-07-31 12:43:48 +01:00
parent f58f299535
commit d197285a56
10 changed files with 41 additions and 43 deletions

View File

@ -44,7 +44,7 @@ public class InstallationPathConfig {
STATIC_PATH = CUSTOM_FILES_PATH + "static" + File.separator; STATIC_PATH = CUSTOM_FILES_PATH + "static" + File.separator;
TEMPLATES_PATH = CUSTOM_FILES_PATH + "templates" + File.separator; TEMPLATES_PATH = CUSTOM_FILES_PATH + "templates" + File.separator;
SIGNATURES_PATH = CUSTOM_FILES_PATH + "signatures" + File.separator; SIGNATURES_PATH = CUSTOM_FILES_PATH + "signatures" + File.separator;
PRIVATE_KEY_PATH = CUSTOM_FILES_PATH + "keys" + File.separator; PRIVATE_KEY_PATH = CONFIG_PATH + "keys" + File.separator;
} }
private static String initializeBasePath() { private static String initializeBasePath() {

View File

@ -186,7 +186,7 @@ public class SecurityConfiguration {
// Configure session management based on JWT setting // Configure session management based on JWT setting
http.sessionManagement( http.sessionManagement(
sessionManagement -> { sessionManagement -> {
if (v2Enabled && !securityProperties.isSaml2Active()) { if (v2Enabled) {
sessionManagement.sessionCreationPolicy( sessionManagement.sessionCreationPolicy(
SessionCreationPolicy.STATELESS); SessionCreationPolicy.STATELESS);
} else { } else {

View File

@ -69,10 +69,11 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
} }
String jwtToken = jwtService.extractToken(request); String jwtToken = jwtService.extractToken(request);
// todo: X-API-KEY
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
// sending a 401 // sending a 401
// todo: any unauthenticated requests should redirect to login
if ("/".equals(request.getRequestURI()) if ("/".equals(request.getRequestURI())
&& "GET".equalsIgnoreCase(request.getMethod())) { && "GET".equalsIgnoreCase(request.getMethod())) {
response.sendRedirect("/login"); response.sendRedirect("/login");

View File

@ -43,13 +43,12 @@ public class JwtService implements JwtServiceInterface {
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 = 3600000;
private final JwtKeystoreServiceInterface keystoreService; private final KeystoreServiceInterface keystoreService;
private final boolean v2Enabled; private final boolean v2Enabled;
@Autowired @Autowired
public JwtService( public JwtService(
@Qualifier("v2Enabled") boolean v2Enabled, @Qualifier("v2Enabled") boolean v2Enabled, KeystoreServiceInterface keystoreService) {
JwtKeystoreServiceInterface keystoreService) {
this.v2Enabled = v2Enabled; this.v2Enabled = v2Enabled;
this.keystoreService = keystoreService; this.keystoreService = keystoreService;
} }
@ -132,7 +131,8 @@ public class JwtService implements JwtServiceInterface {
if (keyId != null) { if (keyId != null) {
log.debug("Looking up key pair for key ID: {}", keyId); log.debug("Looking up key pair for key ID: {}", keyId);
Optional<KeyPair> specificKeyPair = keystoreService.getKeyPairByKeyId(keyId); Optional<KeyPair> specificKeyPair =
keystoreService.getKeyPairByKeyId(keyId); // todo: move to in-memory cache
if (specificKeyPair.isPresent()) { if (specificKeyPair.isPresent()) {
keyPair = specificKeyPair.get(); keyPair = specificKeyPair.get();
@ -178,13 +178,8 @@ public class JwtService implements JwtServiceInterface {
@Override @Override
public String extractToken(HttpServletRequest request) { public String extractToken(HttpServletRequest request) {
String authHeader = request.getHeader(AUTHORIZATION_HEADER);
if (authHeader != null && authHeader.startsWith(BEARER_PREFIX)) {
return authHeader.substring(BEARER_PREFIX.length());
}
Cookie[] cookies = request.getCookies(); Cookie[] cookies = request.getCookies();
if (cookies != null) { if (cookies != null) {
for (Cookie cookie : cookies) { for (Cookie cookie : cookies) {
if (JWT_COOKIE_NAME.equals(cookie.getName())) { if (JWT_COOKIE_NAME.equals(cookie.getName())) {
@ -203,7 +198,7 @@ public class JwtService implements JwtServiceInterface {
ResponseCookie cookie = ResponseCookie cookie =
ResponseCookie.from(JWT_COOKIE_NAME, Newlines.stripAll(token)) ResponseCookie.from(JWT_COOKIE_NAME, Newlines.stripAll(token))
.httpOnly(true) .httpOnly(true)
.secure(true) // .secure(true) // todo: fix, make configurable
.sameSite("Strict") .sameSite("Strict")
.maxAge(EXPIRATION / 1000) .maxAge(EXPIRATION / 1000)
.path("/") .path("/")

View File

@ -17,6 +17,8 @@ import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.common.configuration.InstallationPathConfig; import stirling.software.common.configuration.InstallationPathConfig;
@ -27,16 +29,16 @@ import stirling.software.proprietary.security.model.JwtSigningKey;
@Slf4j @Slf4j
@Service @Service
@ConditionalOnBooleanProperty("v2") @ConditionalOnBooleanProperty("v2")
public class JwtKeyCleanupService { public class KeyPairCleanupService {
private final JwtSigningKeyRepository signingKeyRepository; private final JwtSigningKeyRepository signingKeyRepository;
private final JwtKeystoreService keystoreService; private final KeystoreService keystoreService;
private final ApplicationProperties.Security.Jwt jwtProperties; private final ApplicationProperties.Security.Jwt jwtProperties;
@Autowired @Autowired
public JwtKeyCleanupService( public KeyPairCleanupService(
JwtSigningKeyRepository signingKeyRepository, JwtSigningKeyRepository signingKeyRepository,
JwtKeystoreService keystoreService, KeystoreService keystoreService,
ApplicationProperties applicationProperties) { ApplicationProperties applicationProperties) {
this.signingKeyRepository = signingKeyRepository; this.signingKeyRepository = signingKeyRepository;
this.keystoreService = keystoreService; this.keystoreService = keystoreService;
@ -44,6 +46,7 @@ public class JwtKeyCleanupService {
} }
@Transactional @Transactional
@PostConstruct
@Scheduled(fixedDelay = 24, timeUnit = TimeUnit.HOURS) @Scheduled(fixedDelay = 24, timeUnit = TimeUnit.HOURS)
public void cleanup() { public void cleanup() {
if (!jwtProperties.isEnableKeyCleanup() || !keystoreService.isKeystoreEnabled()) { if (!jwtProperties.isEnableKeyCleanup() || !keystoreService.isKeystoreEnabled()) {
@ -113,7 +116,7 @@ public class JwtKeyCleanupService {
} }
Path privateKeyDirectory = Paths.get(InstallationPathConfig.getPrivateKeyPath()); Path privateKeyDirectory = Paths.get(InstallationPathConfig.getPrivateKeyPath());
Path keyFile = privateKeyDirectory.resolve(keyId + JwtKeystoreService.KEY_SUFFIX); Path keyFile = privateKeyDirectory.resolve(keyId + KeystoreService.KEY_SUFFIX);
if (Files.exists(keyFile)) { if (Files.exists(keyFile)) {
Files.delete(keyFile); Files.delete(keyFile);

View File

@ -33,7 +33,7 @@ import stirling.software.proprietary.security.model.JwtSigningKey;
@Slf4j @Slf4j
@Service @Service
public class JwtKeystoreService implements JwtKeystoreServiceInterface { public class KeystoreService implements KeystoreServiceInterface {
public static final String KEY_SUFFIX = ".key"; public static final String KEY_SUFFIX = ".key";
private final JwtSigningKeyRepository signingKeyRepository; private final JwtSigningKeyRepository signingKeyRepository;
@ -43,7 +43,7 @@ public class JwtKeystoreService implements JwtKeystoreServiceInterface {
private volatile String currentKeyId; private volatile String currentKeyId;
@Autowired @Autowired
public JwtKeystoreService( public KeystoreService(
JwtSigningKeyRepository signingKeyRepository, JwtSigningKeyRepository signingKeyRepository,
ApplicationProperties applicationProperties) { ApplicationProperties applicationProperties) {
this.signingKeyRepository = signingKeyRepository; this.signingKeyRepository = signingKeyRepository;
@ -53,7 +53,6 @@ public class JwtKeystoreService implements JwtKeystoreServiceInterface {
@PostConstruct @PostConstruct
public void initializeKeystore() { public void initializeKeystore() {
if (!isKeystoreEnabled()) { if (!isKeystoreEnabled()) {
log.info("Keystore is disabled, using in-memory key generation");
return; return;
} }
@ -61,7 +60,7 @@ public class JwtKeystoreService implements JwtKeystoreServiceInterface {
ensurePrivateKeyDirectoryExists(); ensurePrivateKeyDirectoryExists();
loadOrGenerateKeypair(); loadOrGenerateKeypair();
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to initialize keystore, falling back to in-memory generation", e); log.error("Failed to initialize keystore, using in-memory generation", e);
} }
} }

View File

@ -3,7 +3,7 @@ package stirling.software.proprietary.security.service;
import java.security.KeyPair; import java.security.KeyPair;
import java.util.Optional; import java.util.Optional;
public interface JwtKeystoreServiceInterface { public interface KeystoreServiceInterface {
KeyPair getActiveKeyPair(); KeyPair getActiveKeyPair();

View File

@ -55,7 +55,7 @@ class JwtServiceTest {
private HttpServletResponse response; private HttpServletResponse response;
@Mock @Mock
private JwtKeystoreServiceInterface keystoreService; private KeystoreServiceInterface keystoreService;
private JwtService jwtService; private JwtService jwtService;
private KeyPair testKeyPair; private KeyPair testKeyPair;

View File

@ -27,13 +27,13 @@ import stirling.software.proprietary.security.database.repository.JwtSigningKeyR
import stirling.software.proprietary.security.model.JwtSigningKey; import stirling.software.proprietary.security.model.JwtSigningKey;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class JwtKeyCleanupServiceTest { class KeyPairCleanupServiceTest {
@Mock @Mock
private JwtSigningKeyRepository signingKeyRepository; private JwtSigningKeyRepository signingKeyRepository;
@Mock @Mock
private JwtKeystoreService keystoreService; private KeystoreService keystoreService;
@Mock @Mock
private ApplicationProperties applicationProperties; private ApplicationProperties applicationProperties;
@ -47,7 +47,7 @@ class JwtKeyCleanupServiceTest {
@TempDir @TempDir
private Path tempDir; private Path tempDir;
private JwtKeyCleanupService cleanupService; private KeyPairCleanupService cleanupService;
@BeforeEach @BeforeEach
void setUp() { void setUp() {
@ -59,7 +59,7 @@ class JwtKeyCleanupServiceTest {
lenient().when(jwtConfig.getCleanupBatchSize()).thenReturn(100); lenient().when(jwtConfig.getCleanupBatchSize()).thenReturn(100);
lenient().when(keystoreService.isKeystoreEnabled()).thenReturn(true); lenient().when(keystoreService.isKeystoreEnabled()).thenReturn(true);
cleanupService = new JwtKeyCleanupService(signingKeyRepository, keystoreService, applicationProperties); cleanupService = new KeyPairCleanupService(signingKeyRepository, keystoreService, applicationProperties);
} }
@ -101,7 +101,7 @@ class JwtKeyCleanupServiceTest {
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());
createTestKeyFile("key-1"); createTestKeyFile("key-1");
createTestKeyFile("key-2"); createTestKeyFile("key-2");
@ -134,7 +134,7 @@ class JwtKeyCleanupServiceTest {
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());
createTestKeyFile("key-1"); createTestKeyFile("key-1");
createTestKeyFile("key-2"); createTestKeyFile("key-2");
createTestKeyFile("key-3"); createTestKeyFile("key-3");
@ -161,7 +161,7 @@ class JwtKeyCleanupServiceTest {
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());
createTestKeyFile("key-1"); createTestKeyFile("key-1");
when(signingKeyRepository.countKeysEligibleForCleanup(any(LocalDateTime.class))).thenReturn(2L); when(signingKeyRepository.countKeysEligibleForCleanup(any(LocalDateTime.class))).thenReturn(2L);

View File

@ -32,7 +32,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class JwtKeystoreServiceInterfaceTest { class KeystoreServiceInterfaceTest {
@Mock @Mock
private JwtSigningKeyRepository repository; private JwtSigningKeyRepository repository;
@ -49,7 +49,7 @@ class JwtKeystoreServiceInterfaceTest {
@TempDir @TempDir
Path tempDir; Path tempDir;
private JwtKeystoreService keystoreService; private KeystoreService keystoreService;
private KeyPair testKeyPair; private KeyPair testKeyPair;
@BeforeEach @BeforeEach
@ -70,7 +70,7 @@ class JwtKeystoreServiceInterfaceTest {
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 KeystoreService(repository, applicationProperties);
assertEquals(keystoreEnabled, keystoreService.isKeystoreEnabled()); assertEquals(keystoreEnabled, keystoreService.isKeystoreEnabled());
} }
@ -82,7 +82,7 @@ class JwtKeystoreServiceInterfaceTest {
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 KeystoreService(repository, applicationProperties);
KeyPair result = keystoreService.getActiveKeyPair(); KeyPair result = keystoreService.getActiveKeyPair();
@ -98,7 +98,7 @@ class JwtKeystoreServiceInterfaceTest {
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 KeystoreService(repository, applicationProperties);
keystoreService.initializeKeystore(); keystoreService.initializeKeystore();
KeyPair result = keystoreService.getActiveKeyPair(); KeyPair result = keystoreService.getActiveKeyPair();
@ -122,7 +122,7 @@ class JwtKeystoreServiceInterfaceTest {
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 KeystoreService(repository, applicationProperties);
keystoreService.initializeKeystore(); keystoreService.initializeKeystore();
KeyPair result = keystoreService.getActiveKeyPair(); KeyPair result = keystoreService.getActiveKeyPair();
@ -146,7 +146,7 @@ class JwtKeystoreServiceInterfaceTest {
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 KeystoreService(repository, applicationProperties);
Optional<KeyPair> result = keystoreService.getKeyPairByKeyId(keyId); Optional<KeyPair> result = keystoreService.getKeyPairByKeyId(keyId);
@ -163,7 +163,7 @@ class JwtKeystoreServiceInterfaceTest {
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 KeystoreService(repository, applicationProperties);
Optional<KeyPair> result = keystoreService.getKeyPairByKeyId(keyId); Optional<KeyPair> result = keystoreService.getKeyPairByKeyId(keyId);
@ -177,7 +177,7 @@ class JwtKeystoreServiceInterfaceTest {
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 KeystoreService(repository, applicationProperties);
Optional<KeyPair> result = keystoreService.getKeyPairByKeyId("any-key"); Optional<KeyPair> result = keystoreService.getKeyPairByKeyId("any-key");
@ -191,7 +191,7 @@ class JwtKeystoreServiceInterfaceTest {
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 KeystoreService(repository, applicationProperties);
keystoreService.initializeKeystore(); keystoreService.initializeKeystore();
assertTrue(Files.exists(tempDir)); assertTrue(Files.exists(tempDir));
@ -209,7 +209,7 @@ class JwtKeystoreServiceInterfaceTest {
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 KeystoreService(repository, applicationProperties);
keystoreService.initializeKeystore(); keystoreService.initializeKeystore();
KeyPair result = keystoreService.getActiveKeyPair(); KeyPair result = keystoreService.getActiveKeyPair();