clean up and more

This commit is contained in:
Ludy87 2025-04-14 18:58:34 +02:00
parent e76e83c427
commit 4ed49d0c15
No known key found for this signature in database
GPG Key ID: 92696155E0220F94
9 changed files with 89 additions and 177 deletions

@ -151,10 +151,10 @@ public class EndpointInterceptor implements HandlerInterceptor {
if ((userSessions >= maxUserSessions
|| totalSessionsNonExpired >= maxApplicationSessions)
&& !hasUserActiveSession) {
response.sendError(
HttpServletResponse.SC_UNAUTHORIZED,
log.info(
"Max sessions reached for this user. To continue on this device, please"
+ " close your session in another browser.");
response.sendError(HttpServletResponse.SC_EXPECTATION_FAILED);
return false;
}
@ -203,10 +203,10 @@ public class EndpointInterceptor implements HandlerInterceptor {
if (totalSessions >= maxApplicationSessions && !hasUserActiveSession) {
sessionsInterface.removeSession(finalSession);
response.sendError(
HttpServletResponse.SC_UNAUTHORIZED,
log.info(
"Max sessions reached for this user. To continue on this device, please"
+ " close your session in another browser.");
response.sendError(HttpServletResponse.SC_EXPECTATION_FAILED);
return false;
}
if (!hasUserActiveSession) {

@ -193,8 +193,15 @@ public class AnonymusSessionListener implements HttpSessionListener, SessionsInt
@Override
public void removeSession(HttpSession session) {
AnonymusSessionInfo sessionsInfo = (AnonymusSessionInfo) sessions.get(session.getId());
sessionsInfo.setExpired(true);
session.invalidate();
if (sessionsInfo != null) {
sessionsInfo.setExpired(true);
}
try {
session.invalidate();
} catch (IllegalStateException e) {
log.debug("Session {} already invalidated", session.getId());
}
sessions.remove(session.getId());
sessions.remove(session.getId());
}

@ -1,9 +1,10 @@
package stirling.software.SPDF.config.anonymus.session;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@ -12,72 +13,44 @@ import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.interfaces.SessionsModelInterface;
@Controller
@Slf4j
public class AnonymusSessionStatusController {
@Autowired private AnonymusSessionListener sessionRegistry;
@GetMapping("/session/status")
public ResponseEntity<String> getSessionStatus(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
// No session found
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("No session found");
}
boolean isActiveSession =
sessionRegistry.getAllSessions().stream()
.filter(s -> s.getSessionId().equals(session.getId()))
.anyMatch(s -> !s.isExpired());
long sessionCount =
sessionRegistry.getAllSessions().stream().filter(s -> !s.isExpired()).count();
long userSessions = sessionCount;
int maxUserSessions = sessionRegistry.getMaxUserSessions();
// Session invalid or expired
if (userSessions >= maxUserSessions && !isActiveSession) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body("Session invalid or expired");
}
// Valid session
else if (session.getId() != null && isActiveSession) {
return ResponseEntity.ok("Valid session: " + session.getId());
}
// Fallback message with session count
else {
return ResponseEntity.ok("User has " + userSessions + " sessions");
}
}
@GetMapping("/session/expire")
public String expireSession(HttpServletRequest request) {
@GetMapping("/userSession")
public String getUserSessions(HttpServletRequest request, Model model) {
HttpSession session = request.getSession(false);
if (session != null) {
// Invalidate current session
sessionRegistry.expireFirstSession(session.getId());
log.info("Session invalidated: {}", session.getId());
// return ResponseEntity.ok("Session invalidated");
} else {
log.info("No session to invalidate");
// return ResponseEntity.ok("No session to invalidate");
boolean isSessionValid =
sessionRegistry.getAllNonExpiredSessions().stream()
.allMatch(
sessionEntity ->
sessionEntity.getSessionId().equals(session.getId()));
// Get all sessions for the user
List<SessionsModelInterface> sessionList =
sessionRegistry.getAllNonExpiredSessions().stream()
.filter(
sessionEntity ->
!sessionEntity.getSessionId().equals(session.getId()))
.toList();
model.addAttribute("sessionList", sessionList);
return "userSession";
}
return "redirect:/";
}
@GetMapping("/session/expire/all")
public ResponseEntity<String> expireAllSessions() {
// Invalidate all sessions
sessionRegistry.expireAllSessions();
return ResponseEntity.ok("All sessions invalidated");
}
@GetMapping("/session/expire/{username}")
public ResponseEntity<String> expireAllSessionsByUsername(@PathVariable String username) {
// Invalidate all sessions for specific user
sessionRegistry.expireAllSessionsByUsername(username);
return ResponseEntity.ok("All sessions invalidated for user: " + username);
@GetMapping("/userSession/invalidate/{sessionId}")
public String invalidateUserSession(
HttpServletRequest request, @PathVariable String sessionId) {
sessionRegistry.expireSession(sessionId);
sessionRegistry.registerSession(request.getSession(false));
return "redirect:/userSession";
}
}

@ -142,25 +142,25 @@ public class CustomHttpSessionListener implements HttpSessionListener, SessionsI
.filter(s -> !s.isExpired() && s.getPrincipalName().equals(principalName))
.toList()
.size();
boolean isAnonymousUserWithoutLogin = "anonymousUser".equals(principalName) && loginEnabled;
boolean isAnonymousUserWithLogin = "anonymousUser".equals(principalName) && loginEnabled;
log.info(
"all {} allNonExpiredSessions {} {} isAnonymousUserWithoutLogin {}",
"all {} allNonExpiredSessions {} {} isAnonymousUserWithLogin {}",
all,
allNonExpiredSessions,
getMaxUserSessions(),
isAnonymousUserWithoutLogin);
isAnonymousUserWithLogin);
if (allNonExpiredSessions >= getMaxApplicationSessions() && !isAnonymousUserWithoutLogin) {
if (allNonExpiredSessions >= getMaxApplicationSessions() && !isAnonymousUserWithLogin) {
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) {
} else if (all >= getMaxUserSessions() && !isAnonymousUserWithLogin) {
enforceMaxSessionsForPrincipal(principalName);
log.info("Session {} Expired=TRUE", session.getId());
} else if (isAnonymousUserWithoutLogin) {
} else if (isAnonymousUserWithLogin) {
sessionPersistentRegistry.expireSession(session.getId());
sessionPersistentRegistry.removeSessionInformation(se.getSession().getId());
} else {

@ -3,11 +3,8 @@ package stirling.software.SPDF.config.anonymus.session;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
@ -29,35 +26,32 @@ import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
@Slf4j
public class SessionStatusController {
@Qualifier("loginEnabled")
private boolean loginEnabled;
@Autowired private SessionPersistentRegistry sessionPersistentRegistry;
@Autowired private SessionsInterface sessionInterface;
@Autowired private CustomHttpSessionListener customHttpSessionListener;
// Returns the current session ID or 401 if no session exists
@GetMapping("/session")
public ResponseEntity<String> getSession(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("No session found");
} else {
return ResponseEntity.ok(session.getId());
}
}
// list all sessions from authentication user, return String redirect userSession.html
@GetMapping("/userSession")
public String getUserSessions(
HttpServletRequest request, Model model, Authentication authentication) {
if (authentication == null || !authentication.isAuthenticated()) {
if ((authentication == null || !authentication.isAuthenticated()) && loginEnabled) {
return "redirect:/login";
}
HttpSession session = request.getSession(false);
if (session != null) {
Object principal = authentication.getPrincipal();
String principalName = UserUtils.getUsernameFromPrincipal(principal);
if (principalName == null) {
return "redirect:/login";
String principalName = null;
if (authentication != null && authentication.isAuthenticated()) {
Object principal = authentication.getPrincipal();
principalName = UserUtils.getUsernameFromPrincipal(principal);
if (principalName == null) {
return "redirect:/login";
}
} else {
principalName = "anonymousUser";
}
boolean isSessionValid =
@ -139,81 +133,4 @@ public class SessionStatusController {
return "redirect:/login";
}
}
// Checks if the session is active and valid according to user session limits
@GetMapping("/session/status")
public ResponseEntity<String> getSessionStatus(HttpServletRequest request) {
HttpSession session = request.getSession(false);
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
Object principalTest = authentication.getPrincipal();
String username = UserUtils.getUsernameFromPrincipal(principalTest);
List<SessionInformation> allSessions =
sessionPersistentRegistry.getAllSessions(username, false);
boolean isActivSession =
sessionPersistentRegistry.getAllSessions().stream()
.filter(
sessionEntity ->
session.getId().equals(sessionEntity.getSessionId()))
.anyMatch(sessionEntity -> !sessionEntity.isExpired());
int userSessions = allSessions.size();
int maxUserSessions = sessionInterface.getMaxUserSessions();
// Check if the current session is valid or expired based on the session registry
if (userSessions >= maxUserSessions && !isActivSession) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body("Session invalid or expired");
} else if (session.getId() != null && isActivSession) {
return ResponseEntity.ok("Valid session: " + session.getId());
} else {
return ResponseEntity.ok(
"User: " + username + " has " + userSessions + " sessions");
}
} else {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body("Session invalid or expired");
}
}
// Invalidates the current session
@GetMapping("/session/expire")
public ResponseEntity<String> expireSession(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session != null) {
sessionPersistentRegistry.expireSession(session.getId());
return ResponseEntity.ok("Session invalidated");
} else {
return ResponseEntity.ok("No session to invalidate");
}
}
// Invalidates all sessions
@GetMapping("/session/expire/all")
public ResponseEntity<String> expireAllSessions() {
log.debug("Expire all sessions");
sessionPersistentRegistry.expireAllSessions();
return ResponseEntity.ok("All sessions invalidated");
}
// Invalidates all sessions for a specific user, only if requested by the same user
@GetMapping("/session/expire/{username}")
public ResponseEntity<String> expireAllSessionsByUsername(@PathVariable String username) {
SecurityContext cxt = SecurityContextHolder.getContext();
Authentication auth = cxt.getAuthentication();
if (auth != null && auth.isAuthenticated()) {
Object principal = auth.getPrincipal();
String principalName = UserUtils.getUsernameFromPrincipal(principal);
if (principalName.equals(username)) {
sessionPersistentRegistry.expireAllSessionsByUsername(username);
return ResponseEntity.ok("All sessions invalidated for user: " + username);
} else {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Unauthorized");
}
}
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Unauthorized");
}
}

@ -287,6 +287,14 @@ database.notSupported=Diese Funktion ist für deine Datenbankverbindung nicht ve
session.expired=Ihre Sitzung ist abgelaufen. Bitte laden Sie die Seite neu und versuchen Sie es erneut.
session.refreshPage=Seite aktualisieren
#################
# USER SESSION #
#################
userSession.title=Benutzersitzungen
userSession.header=Benutzersitzungen
userSession.maxUserSession=Wenn die maximale Anzahl Sitzungen für diesen Benutzer erreicht ist, können Sie hier andere Anmeldungen beenden, um auf diesem Gerät fortzufahren.
userSession.lastRequest=Letzte Aufrufe
#############
# HOME-PAGE #
#############

@ -287,6 +287,14 @@ database.notSupported=This function is not available for your database connectio
session.expired=Your session has expired. Please refresh the page and try again.
session.refreshPage=Refresh Page
#################
# USER SESSION #
#################
userSession.title=User Sessions
userSession.header=User Sessions
userSession.maxUserSession=If the maximum number of sessions for this user is reached, you can end other logins here to continue on this device.
userSession.lastRequest=last Request
#############
# HOME-PAGE #
#############

@ -15,8 +15,7 @@
<p class="lead" th:if="${param.status == '404'}" th:text="#{error.404.1}"></p>
<p class="lead" th:unless="${param.status == '404'}" th:text="#{error.404.2}"></p>
<p class="lead" th:if="${status == 417}">
<a th:href="@{'/userSession'}" th:text="#{session.maxUserSession}">Max sessions reached for this user. To continue on this device, please close your
session in another browser.</a>
<a th:href="@{'/userSession'}" th:text="#{userSession.maxUserSession}">Max sessions reached for this user.</a>
</p>
<br>
<h2 th:text="#{error.needHelp}"></h2>

@ -3,7 +3,7 @@
xmlns:th="https://www.thymeleaf.org">
<head>
<th:block th:insert="~{fragments/common :: head(title=#{session.title}, header=#{session.header})}"></th:block>
<th:block th:insert="~{fragments/common :: head(title=#{userSession.title}, header=#{userSession.header})}"></th:block>
</head>
<body>
@ -16,21 +16,21 @@
<div class="col-md-9 bg-card">
<div class="tool-header">
<span class="material-symbols-rounded tool-header-icon organize">key</span>
<span class="tool-header-text" th:text="#{session.user}">User Session</span>
<span class="tool-header-text" th:text="#{userSession.title}">User Session</span>
</div>
<div class="bg-card mt-3 mb-3">
<span th:text="#{session.maxUserSession}">Max sessions reached for this user. To continue on this device, please close your session in another browser.</span>
<table class="table table-striped table-hover mb-0">
<span th:text="#{userSession.maxUserSession}">Max sessions reached for this user.</span>
<table th:unless="${#lists.isEmpty(sessionList)}" class="table table-striped table-hover mb-0">
<thead>
<tr>
<th scope="col" th:text="#{session.sessionId}">Session ID</th>
<th scope="col" th:text="#{session.lastRequest}">last Request</th>
<th scope="col" th:text="#{session.invalidate}">Logout</th>
<!-- <th scope="col" th:text="#{session.sessionId}">Session ID</th> -->
<th scope="col" th:text="#{userSession.lastRequest}">last Request</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr th:each="userSession : ${sessionList}">
<td th:text="${userSession.getSessionId}"></td>
<!-- <td th:text="${userSession.getSessionId}"></td> -->
<td th:text="${userSession.getLastRequest}"></td>
<td><a th:href="@{/userSession/invalidate/{id}(id=${userSession.getSessionId})}"
class="btn btn-sm btn-danger">