diff --git a/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java b/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java index d37a7595b..48df4f948 100644 --- a/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java +++ b/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java @@ -304,8 +304,8 @@ public class ApplicationProperties { private boolean enableKeystore = true; private boolean enableKeyRotation = false; private boolean enableKeyCleanup = true; - private int keyRetentionDays; - private int cleanupBatchSize; + private int keyRetentionDays = 7; + private int cleanupBatchSize = 100; } } diff --git a/app/core/src/main/resources/settings.yml.template b/app/core/src/main/resources/settings.yml.template index 9ec94beb3..50a2fc98f 100644 --- a/app/core/src/main/resources/settings.yml.template +++ b/app/core/src/main/resources/settings.yml.template @@ -61,10 +61,10 @@ security: spCert: classpath:certificate.crt # Your signing certificate. Generated from your keypair jwt: enableKeyStore: true # Set to 'true' to enable JWT key store - enableKeyRotation: true # Set to 'true' to enable JWT key rotation + enableKeyRotation: false # 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 + keyRetentionDays: 7 # Number of days to retain old keys. The default is 7 days. + cleanupBatchSize: 100 # Number of keys to clean up in each batch. The default is 100. premium: key: 00000000-0000-0000-0000-000000000000 diff --git a/app/core/src/main/resources/static/js/fetch-utils.js b/app/core/src/main/resources/static/js/fetch-utils.js index 5946dc100..73c26ffb3 100644 --- a/app/core/src/main/resources/static/js/fetch-utils.js +++ b/app/core/src/main/resources/static/js/fetch-utils.js @@ -62,11 +62,15 @@ window.JWTManager = { fetch('/logout', { method: 'POST', credentials: 'include' - }).then(() => { - window.location.href = '/login'; + }).then(response => { + if (response.redirected) { + window.location.href = response.url; + } else { + window.location.href = '/login?logout=true'; + } }).catch(() => { - // Even if logout fails, redirect to login - window.location.href = '/login'; + // If logout fails, let server handle it + window.location.href = '/logout'; }); } }; diff --git a/app/core/src/main/resources/static/js/jwt-init.js b/app/core/src/main/resources/static/js/jwt-init.js index 4a8218c47..a2733bf35 100644 --- a/app/core/src/main/resources/static/js/jwt-init.js +++ b/app/core/src/main/resources/static/js/jwt-init.js @@ -47,11 +47,14 @@ console.log('User is not authenticated or token expired'); // Only redirect to login if we're not already on login/register pages const currentPath = window.location.pathname; + const currentSearch = window.location.search; + // Don't redirect if we're on logout page or already being logged out if (!currentPath.includes('/login') && !currentPath.includes('/register') && !currentPath.includes('/oauth') && !currentPath.includes('/saml') && - !currentPath.includes('/error')) { + !currentPath.includes('/error') && + !currentSearch.includes('logout=true')) { // Redirect to login after a short delay to allow other scripts to load setTimeout(() => { window.location.href = '/login'; diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/config/AccountWebController.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/config/AccountWebController.java index 830f8f195..46d0e7d3d 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/config/AccountWebController.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/config/AccountWebController.java @@ -77,8 +77,11 @@ public class AccountWebController { @GetMapping("/login") public String login(HttpServletRequest request, Model model, Authentication authentication) { - // If the user is already authenticated, redirect them to the home page. - if (authentication != null && authentication.isAuthenticated()) { + // If the user is already authenticated and it's not a logout scenario, redirect them to the + // home page. + if (authentication != null + && authentication.isAuthenticated() + && request.getParameter("logout") == null) { return "redirect:/"; } diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/UserAuthenticationFilter.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/UserAuthenticationFilter.java index f6074ceff..f51a9d543 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/UserAuthenticationFilter.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/UserAuthenticationFilter.java @@ -65,13 +65,6 @@ public class UserAuthenticationFilter extends OncePerRequestFilter { String requestURI = request.getRequestURI(); Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - log.info( - "UserAuthenticationFilter - Authentication from SecurityContext: {}", - authentication != null - ? authentication.getClass().getSimpleName() - + " for " - + authentication.getName() - : "null"); // Check for session expiration (unsure if needed) // if (authentication != null && authentication.isAuthenticated()) { @@ -117,7 +110,7 @@ public class UserAuthenticationFilter extends OncePerRequestFilter { String method = request.getMethod(); String contextPath = request.getContextPath(); - if ("GET".equalsIgnoreCase(method) && !(contextPath + "/login").equals(requestURI)) { + if ("GET".equalsIgnoreCase(method) && !requestURI.startsWith(contextPath + "/login")) { response.sendRedirect(contextPath + "/login"); // redirect to the login page } else { response.setStatus(HttpStatus.UNAUTHORIZED.value()); diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/model/AuthenticationType.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/model/AuthenticationType.java index cf9f15e35..c92c1655e 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/model/AuthenticationType.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/model/AuthenticationType.java @@ -2,7 +2,8 @@ package stirling.software.proprietary.security.model; public enum AuthenticationType { WEB, - @Deprecated(since = "1.0.2") SSO, + @Deprecated(since = "1.0.2") + SSO, OAUTH2, SAML2 } diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/JwtKeyCleanupService.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/JwtKeyCleanupService.java index 6f5a43b8c..0f18c66e0 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/JwtKeyCleanupService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/JwtKeyCleanupService.java @@ -10,6 +10,7 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.scheduling.annotation.Scheduled; @@ -25,6 +26,7 @@ import stirling.software.proprietary.security.model.JwtSigningKey; @Slf4j @Service +@ConditionalOnBooleanProperty("v2") public class JwtKeyCleanupService { private final JwtSigningKeyRepository signingKeyRepository; diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/JwtService.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/JwtService.java index a8c7db2f5..7ebef867f 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/JwtService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/JwtService.java @@ -41,7 +41,7 @@ public class JwtService implements JwtServiceInterface { private static final String AUTHORIZATION_HEADER = "Authorization"; private static final String BEARER_PREFIX = "Bearer "; private static final String ISSUER = "Stirling PDF"; - private static final long EXPIRATION = 300000; // 5 minutes in milliseconds + private static final long EXPIRATION = 3600000; private final JwtKeystoreServiceInterface keystoreService; private final boolean v2Enabled; @@ -127,7 +127,6 @@ public class JwtService implements JwtServiceInterface { private Claims extractAllClaims(String token) { try { - // Extract key ID from token header if present String keyId = extractKeyId(token); KeyPair keyPair;