diff --git a/src/main/java/stirling/software/SPDF/config/EndpointInterceptor.java b/src/main/java/stirling/software/SPDF/config/EndpointInterceptor.java index 4ec70721..b58c5a11 100644 --- a/src/main/java/stirling/software/SPDF/config/EndpointInterceptor.java +++ b/src/main/java/stirling/software/SPDF/config/EndpointInterceptor.java @@ -1,5 +1,7 @@ package stirling.software.SPDF.config; +import java.security.Principal; + import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; @@ -39,6 +41,9 @@ public class EndpointInterceptor implements HandlerInterceptor { } if ("GET".equalsIgnoreCase(request.getMethod())) { + + Principal principal = request.getUserPrincipal(); + if ("/".equals(request.getRequestURI()) || "/login".equals(request.getRequestURI()) || "/home".equals(request.getRequestURI()) @@ -55,20 +60,15 @@ public class EndpointInterceptor implements HandlerInterceptor { || request.getRequestURI().endsWith(".webmanifest") || request.getRequestURI().contains("/files/")) { return true; - } else { + } else if (principal != null) { if (session == null) { session = request.getSession(true); } + final HttpSession finalSession = session; String sessionId = finalSession.getId(); - // Den aktuellen Benutzer (principalName) aus der Session ermitteln. - // Es wird angenommen, dass das Attribut "principalName" in der Session gesetzt - // wurde. - final String currentPrincipal = - finalSession.getAttribute("principalName") != null - ? finalSession.getAttribute("principalName").toString() - : "unknown"; + final String currentPrincipal = principal.getName(); // Zähle alle nicht abgelaufenen Sessions des aktuellen Benutzers. long userSessions = diff --git a/src/main/java/stirling/software/SPDF/config/anonymus/session/AnonymusSessionRegistry.java b/src/main/java/stirling/software/SPDF/config/anonymus/session/AnonymusSessionRegistry.java index a315619f..5cbb967a 100644 --- a/src/main/java/stirling/software/SPDF/config/anonymus/session/AnonymusSessionRegistry.java +++ b/src/main/java/stirling/software/SPDF/config/anonymus/session/AnonymusSessionRegistry.java @@ -42,8 +42,6 @@ public class AnonymusSessionRegistry implements HttpSessionListener, SessionsInt return; } - session.setAttribute("principalName", "anonymousUser"); - // Speichern des Erstellungszeitpunkts Date creationTime = new Date(); diff --git a/src/main/java/stirling/software/SPDF/config/anonymus/session/AnonymusSessionStatusController.java b/src/main/java/stirling/software/SPDF/config/anonymus/session/AnonymusSessionStatusController.java index 79e632f1..5fbc3f78 100644 --- a/src/main/java/stirling/software/SPDF/config/anonymus/session/AnonymusSessionStatusController.java +++ b/src/main/java/stirling/software/SPDF/config/anonymus/session/AnonymusSessionStatusController.java @@ -9,7 +9,6 @@ import org.springframework.web.bind.annotation.RestController; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpSession; -import lombok.extern.slf4j.Slf4j; @RestController public class AnonymusSessionStatusController { diff --git a/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java b/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java index 6ff37a9d..4f0b8537 100644 --- a/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java +++ b/src/main/java/stirling/software/SPDF/config/security/SecurityConfiguration.java @@ -36,6 +36,7 @@ import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService; import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticationFailureHandler; import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticationSuccessHandler; import stirling.software.SPDF.config.security.saml2.CustomSaml2ResponseAuthenticationConverter; +import stirling.software.SPDF.config.security.session.PreLogoutDataCaptureHandler; import stirling.software.SPDF.config.security.session.SessionPersistentRegistry; import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.User; @@ -158,6 +159,8 @@ public class SecurityConfiguration { http.logout( logout -> logout.logoutRequestMatcher(new AntPathRequestMatcher("/logout")) + .addLogoutHandler( + new PreLogoutDataCaptureHandler(sessionRegistry)) .logoutSuccessHandler( new CustomLogoutSuccessHandler(applicationProperties)) .clearAuthentication(true) diff --git a/src/main/java/stirling/software/SPDF/config/security/session/CustomHttpSessionListener.java b/src/main/java/stirling/software/SPDF/config/security/session/CustomHttpSessionListener.java index 05291591..f8ed023c 100644 --- a/src/main/java/stirling/software/SPDF/config/security/session/CustomHttpSessionListener.java +++ b/src/main/java/stirling/software/SPDF/config/security/session/CustomHttpSessionListener.java @@ -10,6 +10,7 @@ import java.util.Date; import java.util.List; import java.util.stream.Collectors; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; @@ -33,13 +34,20 @@ import stirling.software.SPDF.model.SessionEntity; public class CustomHttpSessionListener implements HttpSessionListener, SessionsInterface { private final SessionPersistentRegistry sessionPersistentRegistry; + private final boolean loginEnabled; + private final boolean runningEE; @Value("${server.servlet.session.timeout:120s}") // TODO: Change to 30m private Duration defaultMaxInactiveInterval; - public CustomHttpSessionListener(SessionPersistentRegistry sessionPersistentRegistry) { + public CustomHttpSessionListener( + SessionPersistentRegistry sessionPersistentRegistry, + @Qualifier("loginEnabled") boolean loginEnabled, + @Qualifier("runningEE") boolean runningEE) { super(); this.sessionPersistentRegistry = sessionPersistentRegistry; + this.loginEnabled = loginEnabled; + this.runningEE = runningEE; } @Override @@ -110,18 +118,46 @@ public class CustomHttpSessionListener implements HttpSessionListener, SessionsI if (principalName == null) { return; } - session.setAttribute("principalName", principalName); - if ("anonymousUser".equals(principalName)) { - log.info("Principal is anonymousUser"); + if ("anonymousUser".equals(principalName) && loginEnabled) { + return; } int allNonExpiredSessions = getAllNonExpiredSessions().size(); - if (allNonExpiredSessions >= getMaxUserSessions()) { + + allNonExpiredSessions = + getAllSessions().stream() + .filter(s -> !s.isExpired()) + .filter(s -> s.getPrincipalName().equals(principalName)) + .filter(s -> "anonymousUser".equals(principalName) && !loginEnabled) + .peek(s -> log.info("Session {}", s.getPrincipalName())) + .toList() + .size(); + + int all = + getAllSessions().stream() + .filter(s -> !s.isExpired() && s.getPrincipalName().equals(principalName)) + .toList() + .size(); + boolean isAnonymousUserWithoutLogin = "anonymousUser".equals(principalName) && loginEnabled; + log.info( + "all {} allNonExpiredSessions {} {} isAnonymousUserWithoutLogin {}", + all, + allNonExpiredSessions, + getMaxUserSessions(), + isAnonymousUserWithoutLogin); + + if (allNonExpiredSessions >= getMaxApplicationSessions() && !isAnonymousUserWithoutLogin) { log.info("Session {} Expired=TRUE", session.getId()); sessionPersistentRegistry.expireSession(session.getId()); sessionPersistentRegistry.removeSessionInformation(se.getSession().getId()); // if (allNonExpiredSessions > getMaxUserSessions()) { // enforceMaxSessionsForPrincipal(principalName); // } + } else if (all >= getMaxUserSessions() && !isAnonymousUserWithoutLogin) { + enforceMaxSessionsForPrincipal(principalName); + log.info("Session {} Expired=TRUE", principalName); + } else if (isAnonymousUserWithoutLogin) { + sessionPersistentRegistry.expireSession(session.getId()); + sessionPersistentRegistry.removeSessionInformation(se.getSession().getId()); } else { log.info("Session created: {}", principalName); sessionPersistentRegistry.registerNewSession(se.getSession().getId(), principalName); @@ -164,7 +200,6 @@ public class CustomHttpSessionListener implements HttpSessionListener, SessionsI if (session == null) { return; } - SessionInformation sessionsInfo = sessionPersistentRegistry.getSessionInformation(session.getId()); if (sessionsInfo == null) { @@ -197,4 +232,22 @@ public class CustomHttpSessionListener implements HttpSessionListener, SessionsI sessionPersistentRegistry.removeSessionInformation(session.getId()); log.info("Session {} wurde Expired=TRUE", session.getId()); } + + // Get the maximum number of sessions + @Override + public int getMaxApplicationSessions() { + if (runningEE) { + return Integer.MAX_VALUE; + } + return getMaxUserSessions() * 10; + } + + // Get the maximum number of user sessions + @Override + public int getMaxUserSessions() { + if (runningEE) { + return Integer.MAX_VALUE; + } + return 3; + } } diff --git a/src/main/java/stirling/software/SPDF/config/security/session/PreLogoutDataCaptureHandler.java b/src/main/java/stirling/software/SPDF/config/security/session/PreLogoutDataCaptureHandler.java new file mode 100644 index 00000000..af796c7f --- /dev/null +++ b/src/main/java/stirling/software/SPDF/config/security/session/PreLogoutDataCaptureHandler.java @@ -0,0 +1,45 @@ +package stirling.software.SPDF.config.security.session; + +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.LogoutHandler; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@AllArgsConstructor +public class PreLogoutDataCaptureHandler implements LogoutHandler { + + private final SessionPersistentRegistry sessionPersistentRegistry; + + @Override + public void logout( + HttpServletRequest request, + HttpServletResponse response, + Authentication authentication) { + + HttpSession session = request.getSession(false); + if (session == null) { + return; + } + + String sessionId = session.getId(); + if (sessionId == null) { + return; + } + String path = request.getServletPath(); + if (path == null) { + return; + } + if (!"/logout".equals(path)) { + return; + } + log.debug("Session ID: {} Principal: {}", sessionId, authentication.getPrincipal()); + sessionPersistentRegistry.expireSession(sessionId); + sessionPersistentRegistry.removeSessionInformation(sessionId); + } +} diff --git a/src/main/java/stirling/software/SPDF/config/security/session/SessionScheduled.java b/src/main/java/stirling/software/SPDF/config/security/session/SessionScheduled.java index 5fe82f09..25ba4532 100644 --- a/src/main/java/stirling/software/SPDF/config/security/session/SessionScheduled.java +++ b/src/main/java/stirling/software/SPDF/config/security/session/SessionScheduled.java @@ -38,6 +38,7 @@ public class SessionScheduled { } else if (principal instanceof String stringPrincipal) { // Skip anonymousUser if login is enabled if ("anonymousUser".equals(stringPrincipal) && loginEnabledValue) { + sessionPersistentRegistry.expireAllSessionsByPrincipalName(stringPrincipal); continue; } } @@ -49,18 +50,13 @@ public class SessionScheduled { Instant expirationTime = lastRequest.toInstant().plus(maxInactiveInterval, ChronoUnit.SECONDS); if (now.isAfter(expirationTime)) { - log.info( - "SessionID: {} expiration time: {} Current time: {}", - sessionInformation.getSessionId(), - expirationTime, - now); sessionPersistentRegistry.expireSession(sessionInformation.getSessionId()); sessionInformation.expireNow(); if (authentication != null && principal.equals(authentication.getPrincipal())) { authentication.setAuthenticated(false); } SecurityContextHolder.clearContext(); - log.info( + log.debug( "Session expired for principal: {} SessionID: {}", principal, sessionInformation.getSessionId()); diff --git a/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java b/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java index 17018408..f093d662 100644 --- a/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java +++ b/src/main/java/stirling/software/SPDF/controller/web/AccountWebController.java @@ -348,7 +348,6 @@ public class AccountWebController { model.addAttribute("maxSessions", maxSessions); model.addAttribute("maxUserSessions", maxUserSessions); model.addAttribute("sessionCount", sessionCount); - model.addAttribute("maxEnterpriseUsers", applicationProperties.getPremium().getMaxUsers()); model.addAttribute("maxPaidUsers", applicationProperties.getPremium().getMaxUsers()); return "adminSettings"; } diff --git a/src/main/resources/templates/adminSettings.html b/src/main/resources/templates/adminSettings.html index a3759f9f..5ffecd57 100644 --- a/src/main/resources/templates/adminSettings.html +++ b/src/main/resources/templates/adminSettings.html @@ -59,8 +59,8 @@
- Total Users: + Total Users: + Active Users: @@ -70,11 +70,13 @@ Total - Sessions: + Sessions: + Total - Sessions: / + Sessions: + /