add runningEE and more

This commit is contained in:
Ludy87 2025-03-23 21:18:54 +01:00
parent e7b3fd0859
commit 969ca7be50
No known key found for this signature in database
GPG Key ID: 92696155E0220F94
6 changed files with 63 additions and 41 deletions

View File

@ -1,6 +1,7 @@
package stirling.software.SPDF.config.security;
import java.io.IOException;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@ -66,43 +67,50 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
Object principalTest = authentication.getPrincipal();
String username = UserUtils.getUsernameFromPrincipal(principalTest);
log.info("Principal: {}", username);
List<SessionInformation> allSessions =
sessionPersistentRegistry.getAllSessions(username, false);
int userSessions = allSessions.size();
HttpSession session = request.getSession(false);
if (session == null) {
session = request.getSession(true);
filterChain.doFilter(request, response);
return;
}
String sessionId = session.getId();
String sessionId = request.getSession(false).getId();
log.info("allSessions: {} username: {}", allSessions.size(), username);
if (allSessions.size() > 2) {
// Sortiere nach letzter Aktivität älteste zuerst
List<SessionInformation> sortedSessions =
allSessions.stream()
.sorted(Comparator.comparing(SessionInformation::getLastRequest))
.collect(Collectors.toList());
int sessionsToExpire = allSessions.size() - 2;
for (int i = 0; i < sessionsToExpire; i++) {
SessionInformation oldSession = sortedSessions.get(i);
if (!sessionId.equals(oldSession.getSessionId())) {
sessionPersistentRegistry.expireSession(oldSession.getSessionId());
oldSession.expireNow();
log.info(
"Expired old session: {} (last request: {})",
oldSession.getSessionId(),
oldSession.getLastRequest());
}
}
}
for (SessionInformation sessionInformation : allSessions) {
if (sessionId.equals(sessionInformation.getSessionId())) {
log.info("Session found: {}", sessionId);
log.info("lastRequest: {}", sessionInformation.getLastRequest());
sessionPersistentRegistry.refreshLastRequest(sessionId);
SessionInformation sessionInfo =
sessionPersistentRegistry.getSessionInformation(sessionId);
log.info("new lastRequest: {}", sessionInfo.getLastRequest());
} else if (allSessions.size() > 2) {
sessionPersistentRegistry.expireSession(sessionId);
sessionInformation.expireNow();
authentication.setAuthenticated(false);
SecurityContextHolder.clearContext();
request.getSession().invalidate();
log.info(
"Expired session: {} Date: {}",
sessionInformation.getSessionId(),
sessionInformation.getLastRequest());
response.sendRedirect(request.getContextPath() + "/login?error=expiredSession");
return;
}
}
allSessions = sessionPersistentRegistry.getAllSessions(username, false);
log.info(
"username: {} || before Sessions: {} | after Sessions: {}",
username,
userSessions,
allSessions.size());
SessionEntity sessionEntity = sessionPersistentRegistry.getSessionEntity(sessionId);
if (allSessions.isEmpty() || sessionEntity.isExpired()) {

View File

@ -7,6 +7,7 @@ import java.util.Date;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.session.SessionRegistry;
@ -24,11 +25,14 @@ import stirling.software.SPDF.model.SessionEntity;
public class SessionPersistentRegistry implements SessionRegistry {
private final SessionRepository sessionRepository;
private final boolean runningEE;
@Value("${server.servlet.session.timeout:120s}") // TODO: Change to 30m
private Duration defaultMaxInactiveInterval;
public SessionPersistentRegistry(SessionRepository sessionRepository) {
public SessionPersistentRegistry(
SessionRepository sessionRepository, @Qualifier("runningEE") boolean runningEE) {
this.runningEE = runningEE;
this.sessionRepository = sessionRepository;
}
@ -184,4 +188,12 @@ public class SessionPersistentRegistry implements SessionRegistry {
// The first session in the list is the latest session for the given principal name
return Optional.of(allSessions.get(0));
}
// Get the maximum number of sessions
public int getMaxSessions() {
if (runningEE) {
return Integer.MAX_VALUE;
}
return 30;
}
}

View File

@ -1,5 +1,6 @@
package stirling.software.SPDF.config.security.session;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.session.SessionRegistryImpl;
@ -14,7 +15,7 @@ public class SessionRegistryConfig {
@Bean
public SessionPersistentRegistry sessionPersistentRegistry(
SessionRepository sessionRepository) {
return new SessionPersistentRegistry(sessionRepository);
SessionRepository sessionRepository, @Qualifier("runningEE") boolean runningEE) {
return new SessionPersistentRegistry(sessionRepository, runningEE);
}
}

View File

@ -13,8 +13,6 @@ import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
@ -30,7 +28,7 @@ import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.security.UserService;
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
import stirling.software.SPDF.config.security.UserUtils;
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
import stirling.software.SPDF.model.AuthenticationType;
import stirling.software.SPDF.model.Role;
@ -293,19 +291,11 @@ public class UserController {
if (!enabled) {
// Invalidate all sessions if the user is being disabled
List<Object> principals = sessionRegistry.getAllPrincipals();
String userNameP = "";
for (Object principal : principals) {
List<SessionInformation> sessionsInformation =
sessionRegistry.getAllSessions(principal, false);
if (principal instanceof UserDetails detailsUser) {
userNameP = detailsUser.getUsername();
} else if (principal instanceof OAuth2User oAuth2User) {
userNameP = oAuth2User.getName();
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
userNameP = saml2User.name();
} else if (principal instanceof String stringUser) {
userNameP = stringUser;
}
String userNameP = UserUtils.getUsernameFromPrincipal(principal);
if (userNameP.equalsIgnoreCase(username)) {
for (SessionInformation sessionInfo : sessionsInformation) {
sessionRegistry.expireSession(sessionInfo.getSessionId());

View File

@ -14,6 +14,7 @@ import java.util.stream.Collectors;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Controller;
@ -205,8 +206,10 @@ public class AccountWebController {
// Map to store session information and user activity status
Map<String, Boolean> userSessions = new HashMap<>();
Map<String, Date> userLastRequest = new HashMap<>();
Map<String, Integer> userActiveSessions = new HashMap<>();
int activeUsers = 0;
int disabledUsers = 0;
int maxSessions = sessionPersistentRegistry.getMaxSessions();
while (iterator.hasNext()) {
User user = iterator.next();
if (user != null) {
@ -221,7 +224,7 @@ public class AccountWebController {
// Determine the user's session status and last request time
int maxInactiveInterval = sessionPersistentRegistry.getMaxInactiveInterval();
boolean hasActiveSession = false;
Date lastRequest = null;
Date lastRequest;
Optional<SessionEntity> latestSession =
sessionPersistentRegistry.findLatestSession(user.getUsername());
if (latestSession.isPresent()) {
@ -251,6 +254,9 @@ public class AccountWebController {
if (!user.isEnabled()) {
disabledUsers++;
}
List<SessionInformation> sessionInformations =
sessionPersistentRegistry.getAllSessions(user.getUsername(), false);
userActiveSessions.put(user.getUsername(), sessionInformations.size());
}
}
// Sort users by active status and last request date
@ -316,9 +322,11 @@ public class AccountWebController {
model.addAttribute("roleDetails", roleDetails);
model.addAttribute("userSessions", userSessions);
model.addAttribute("userLastRequest", userLastRequest);
model.addAttribute("userActiveSessions", userActiveSessions);
model.addAttribute("totalUsers", allUsers.size());
model.addAttribute("activeUsers", activeUsers);
model.addAttribute("disabledUsers", disabledUsers);
model.addAttribute("maxSessions", maxSessions);
return "addUsers";
}

View File

@ -25,7 +25,7 @@
<br><br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-9 bg-card">
<div class="col-md-12 bg-card">
<div class="tool-header">
<span class="material-symbols-rounded tool-header-icon organize">manage_accounts</span>
<span class="tool-header-text" th:text="#{adminUserSettings.header}">Admin User Control Settings</span>
@ -45,6 +45,7 @@
<strong th:text="#{adminUserSettings.totalUsers}">Total Users:</strong> <span th:text="${totalUsers}"></span>
<strong style="margin-left: 20px;" th:text="#{adminUserSettings.activeUsers}">Active Users:</strong> <span th:text="${activeUsers}"></span>
<strong style="margin-left: 20px;" th:text="#{adminUserSettings.disabledUsers}">Disabled Users:</strong> <span th:text="${disabledUsers}"></span>
<strong th:if="${!@runningEE}" style="margin-left: 20px;" th:text="#{adminUserSettings.maxSessions}">Max Sessions</strong> <span th:text="${maxSessions}"></span>
</div>
</div>
<div th:if="${addMessage}" class="p-3" style="background: var(--md-sys-color-outline-variant);border-radius: 2rem; text-align: center;">
@ -69,6 +70,7 @@
<th scope="col" th:title="#{adminUserSettings.roles}" th:text="#{adminUserSettings.roles}">Roles</th>
<th scope="col" th:title="#{adminUserSettings.authenticated}" class="text-overflow" th:text="#{adminUserSettings.authenticated}">Authenticated</th>
<th scope="col" th:title="#{adminUserSettings.lastRequest}" class="text-overflow" th:text="#{adminUserSettings.lastRequest}">Last Request</th>
<th th:if="${!@runningEE}" scope="col" th:title="#{adminUserSettings.userSession}" th:text="#{adminUserSettings.userSessions}" colspan="1">Sessions</th>
<th scope="col" th:title="#{adminUserSettings.actions}" th:text="#{adminUserSettings.actions}" colspan="2">Actions</th>
<!-- <th scope="col"></th> -->
</tr>
@ -80,6 +82,7 @@
<td style="align-content: center;" th:text="#{${user.roleName}}"></td>
<td style="align-content: center;" th:text="${user.authenticationType}"></td>
<td style="align-content: center;" th:text="${userLastRequest[user.username] != null ? #dates.format(userLastRequest[user.username], 'yyyy-MM-dd HH:mm:ss') : 'N/A'}"></td>
<td th:if="${!@runningEE}" style="align-content: center;" th:text="${userActiveSessions[user.username] != null ? userActiveSessions[user.username] : 0}"></td>
<td style="align-content: center;">
<form th:if="${user.username != currentUsername}" th:action="@{'/api/v1/user/admin/deleteUser/' + ${user.username}}" method="post" onsubmit="return confirmDeleteUser()">
<button type="submit" th:title="#{adminUserSettings.deleteUser}" class="btn btn-info btn-sm"><span class="material-symbols-rounded">person_remove</span></button>