logout process

This commit is contained in:
Ludy87 2025-03-28 13:01:31 +01:00
parent 79439f392f
commit 762571f42b
No known key found for this signature in database
GPG Key ID: 92696155E0220F94
9 changed files with 123 additions and 28 deletions

View File

@ -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 =

View File

@ -42,8 +42,6 @@ public class AnonymusSessionRegistry implements HttpSessionListener, SessionsInt
return;
}
session.setAttribute("principalName", "anonymousUser");
// Speichern des Erstellungszeitpunkts
Date creationTime = new Date();

View File

@ -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 {

View File

@ -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)

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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());

View File

@ -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";
}

View File

@ -59,8 +59,8 @@
</a>
<div class="my-4">
<strong style="margin-left: 20px;" th:text="#{adminUserSettings.totalUsers}">Total Users:</strong> <span
th:text="${totalUsers}"></span><span th:if="${@runningProOrHigher}" th:text="'/'+${maxEnterpriseUsers}"></span>
<strong style="margin-left: 20px;" th:text="#{adminUserSettings.totalUsers}">Total Users:</strong>
<span th:text="${totalUsers}"></span>
<span th:if="${@runningProOrHigher}" th:text="'/'+${maxPaidUsers}"></span>
<strong style="margin-left: 20px;" th:text="#{adminUserSettings.activeUsers}">Active Users:</strong>
@ -70,11 +70,13 @@
<span th:text="${disabledUsers}"></span>
<th:block th:if="${@runningProOrHigher}">
<strong style="margin-left: 20px;" th:text="#{adminUserSettings.totalSessions}">Total
Sessions:</strong> <span th:text="${sessionCount}"></span>
Sessions:</strong>
<span th:text="${sessionCount}"></span>
</th:block>
<th:block th:if="${!@runningProOrHigher}">
<strong style="margin-left: 20px;" th:text="#{adminUserSettings.totalSessions}">Total
Sessions:</strong> <span th:text="${sessionCount}"></span>/<span th:text="${maxSessions}"></span>
Sessions:</strong>
<span th:text="${sessionCount}"></span>/<span th:text="${maxSessions}"></span>
</th:block>
</div>
</div>