any more change

This commit is contained in:
Ludy87 2025-03-30 23:12:04 +02:00
parent f33d8b0f23
commit a16b6478a7
No known key found for this signature in database
GPG Key ID: 92696155E0220F94
7 changed files with 101 additions and 47 deletions

View File

@ -80,11 +80,13 @@ public class EndpointInterceptor implements HandlerInterceptor {
.filter(s -> !s.isExpired()) .filter(s -> !s.isExpired())
.count(); .count();
log.debug( int maxUserSessions = sessionsInterface.getMaxUserSessions();
log.info(
"Active sessions for {}: {} (max: {}) | Total: {} (max: {})", "Active sessions for {}: {} (max: {}) | Total: {} (max: {})",
currentPrincipal, currentPrincipal,
userSessions, userSessions,
sessionsInterface.getMaxUserSessions(), maxUserSessions,
totalSessions, totalSessions,
sessionsInterface.getMaxApplicationSessions()); sessionsInterface.getMaxApplicationSessions());
@ -93,7 +95,7 @@ public class EndpointInterceptor implements HandlerInterceptor {
.filter(s -> !s.isExpired()) .filter(s -> !s.isExpired())
.anyMatch(s -> s.getSessionId().equals(sessionId)); .anyMatch(s -> s.getSessionId().equals(sessionId));
if ((userSessions >= sessionsInterface.getMaxUserSessions() if ((userSessions >= maxUserSessions
|| totalSessions >= sessionsInterface.getMaxApplicationSessions()) || totalSessions >= sessionsInterface.getMaxApplicationSessions())
&& !isCurrentSessionRegistered) { && !isCurrentSessionRegistered) {
response.sendError( response.sendError(
@ -129,8 +131,14 @@ public class EndpointInterceptor implements HandlerInterceptor {
.filter(s -> !s.isExpired()) .filter(s -> !s.isExpired())
.anyMatch(s -> s.getSessionId().equals(sessionId)); .anyMatch(s -> s.getSessionId().equals(sessionId));
if (totalSessions >= sessionsInterface.getMaxApplicationSessions() int maxApplicationSessions = sessionsInterface.getMaxApplicationSessions();
&& !isCurrentSessionRegistered) {
log.info(
"Active sessions for anonymous: Total: {} (max: {})",
totalSessions,
maxApplicationSessions);
if (totalSessions >= maxApplicationSessions && !isCurrentSessionRegistered) {
response.sendError( response.sendError(
HttpServletResponse.SC_UNAUTHORIZED, HttpServletResponse.SC_UNAUTHORIZED,
"Max sessions reached for this user. To continue on this device, please" "Max sessions reached for this user. To continue on this device, please"

View File

@ -167,11 +167,6 @@ public class AnonymusSessionRegistry implements HttpSessionListener, SessionsInt
} }
} }
@Override
public int getMaxApplicationSessions() {
return getMaxUserSessions();
}
@Override @Override
public void removeSession(HttpSession session) { public void removeSession(HttpSession session) {
AnonymusSessionInfo sessionsInfo = (AnonymusSessionInfo) sessions.get(session.getId()); AnonymusSessionInfo sessionsInfo = (AnonymusSessionInfo) sessions.get(session.getId());
@ -179,4 +174,14 @@ public class AnonymusSessionRegistry implements HttpSessionListener, SessionsInt
session.invalidate(); session.invalidate();
sessions.remove(session.getId()); sessions.remove(session.getId());
} }
@Override
public int getMaxApplicationSessions() {
return 5;
}
@Override
public int getMaxUsers() {
return 1;
}
} }

View File

@ -21,6 +21,10 @@ public interface SessionsInterface {
} }
default int getMaxApplicationSessions() { default int getMaxApplicationSessions() {
return 10 * getMaxUserSessions(); return getMaxUserSessions() * 3;
}
default int getMaxUsers() {
return 10;
} }
} }

View File

@ -8,6 +8,7 @@ import java.util.Collection;
import java.util.Comparator; import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
@ -27,12 +28,14 @@ import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.interfaces.SessionsInterface; import stirling.software.SPDF.config.interfaces.SessionsInterface;
import stirling.software.SPDF.config.interfaces.SessionsModelInterface; import stirling.software.SPDF.config.interfaces.SessionsModelInterface;
import stirling.software.SPDF.config.security.UserUtils; import stirling.software.SPDF.config.security.UserUtils;
import stirling.software.SPDF.model.ApplicationProperties;
@Component @Component
@Slf4j @Slf4j
public class CustomHttpSessionListener implements HttpSessionListener, SessionsInterface { public class CustomHttpSessionListener implements HttpSessionListener, SessionsInterface {
private final SessionPersistentRegistry sessionPersistentRegistry; private final SessionPersistentRegistry sessionPersistentRegistry;
private final ApplicationProperties applicationProperties;
private final boolean loginEnabled; private final boolean loginEnabled;
private final boolean runningEE; private final boolean runningEE;
@ -42,11 +45,13 @@ public class CustomHttpSessionListener implements HttpSessionListener, SessionsI
public CustomHttpSessionListener( public CustomHttpSessionListener(
SessionPersistentRegistry sessionPersistentRegistry, SessionPersistentRegistry sessionPersistentRegistry,
@Qualifier("loginEnabled") boolean loginEnabled, @Qualifier("loginEnabled") boolean loginEnabled,
@Qualifier("runningEE") boolean runningEE) { @Qualifier("runningEE") boolean runningEE,
ApplicationProperties applicationProperties) {
super(); super();
this.sessionPersistentRegistry = sessionPersistentRegistry; this.sessionPersistentRegistry = sessionPersistentRegistry;
this.loginEnabled = loginEnabled; this.loginEnabled = loginEnabled;
this.runningEE = runningEE; this.runningEE = runningEE;
this.applicationProperties = applicationProperties;
} }
@Override @Override
@ -56,6 +61,14 @@ public class CustomHttpSessionListener implements HttpSessionListener, SessionsI
.toList(); .toList();
} }
public List<SessionsModelInterface> getAllSessions(Object principalName, boolean expired) {
return sessionPersistentRegistry.getAllSessions().stream()
.filter(s -> s.getPrincipalName().equals(principalName))
.filter(s -> expired == s.isExpired())
.sorted(Comparator.comparing(SessionsModelInterface::getLastRequest))
.collect(Collectors.toList());
}
@Override @Override
public Collection<SessionsModelInterface> getAllSessions() { public Collection<SessionsModelInterface> getAllSessions() {
return new ArrayList<>(sessionPersistentRegistry.getAllSessions()); return new ArrayList<>(sessionPersistentRegistry.getAllSessions());
@ -66,6 +79,20 @@ public class CustomHttpSessionListener implements HttpSessionListener, SessionsI
sessionPersistentRegistry.refreshLastRequest(sessionId); sessionPersistentRegistry.refreshLastRequest(sessionId);
} }
public Optional<SessionsModelInterface> findLatestSession(String principalName) {
return getAllSessions(principalName, false).stream()
.filter(s -> s.getPrincipalName().equals(principalName))
.max(Comparator.comparing(SessionsModelInterface::getLastRequest));
}
public void expireSession(String sessionId) {
sessionPersistentRegistry.expireSession(sessionId);
}
public int getMaxInactiveInterval() {
return (int) defaultMaxInactiveInterval.getSeconds();
}
@Override @Override
public void sessionCreated(HttpSessionEvent se) { public void sessionCreated(HttpSessionEvent se) {
HttpSession session = se.getSession(); HttpSession session = se.getSession();
@ -207,21 +234,33 @@ public class CustomHttpSessionListener implements HttpSessionListener, SessionsI
log.debug("Session {} expired=TRUE", session.getId()); log.debug("Session {} expired=TRUE", session.getId());
} }
// Get the maximum number of sessions // Get the maximum number of application sessions
@Override @Override
public int getMaxApplicationSessions() { public int getMaxApplicationSessions() {
if (runningEE) { return getMaxUsers() * getMaxUserSessions();
return Integer.MAX_VALUE;
}
return getMaxUserSessions() * 10;
} }
// Get the maximum number of user sessions // Get the maximum number of user sessions
@Override @Override
public int getMaxUserSessions() { public int getMaxUserSessions() {
if (runningEE) { if (loginEnabled) {
return Integer.MAX_VALUE; return 3;
} }
return 3; return 10;
}
// Get the maximum number of user sessions
@Override
public int getMaxUsers() {
if (loginEnabled) {
if (runningEE) {
int maxUsers = applicationProperties.getPremium().getMaxUsers();
if (maxUsers > 0) {
return maxUsers;
}
}
return 50;
}
return 1;
} }
} }

View File

@ -18,6 +18,7 @@ import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.interfaces.SessionsInterface;
import stirling.software.SPDF.config.security.UserUtils; import stirling.software.SPDF.config.security.UserUtils;
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry; import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
@ -26,6 +27,7 @@ import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
public class SessionStatusController { public class SessionStatusController {
@Autowired private SessionPersistentRegistry sessionPersistentRegistry; @Autowired private SessionPersistentRegistry sessionPersistentRegistry;
@Autowired private SessionsInterface sessionInterface;
// Returns the current session ID or 401 if no session exists // Returns the current session ID or 401 if no session exists
@GetMapping("/session") @GetMapping("/session")
@ -59,7 +61,7 @@ public class SessionStatusController {
.anyMatch(sessionEntity -> !sessionEntity.isExpired()); .anyMatch(sessionEntity -> !sessionEntity.isExpired());
int userSessions = allSessions.size(); int userSessions = allSessions.size();
int maxUserSessions = sessionPersistentRegistry.getMaxUserSessions(); int maxUserSessions = sessionInterface.getMaxUserSessions();
// Check if the current session is valid or expired based on the session registry // Check if the current session is valid or expired based on the session registry
if (userSessions >= maxUserSessions && !isActivSession) { if (userSessions >= maxUserSessions && !isActivSession) {

View File

@ -14,7 +14,6 @@ import java.util.Optional;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
@ -30,8 +29,9 @@ import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.config.interfaces.SessionsModelInterface;
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal; import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry; import stirling.software.SPDF.config.security.session.CustomHttpSessionListener;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.ApplicationProperties.Security; import stirling.software.SPDF.model.ApplicationProperties.Security;
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2; import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
@ -54,7 +54,7 @@ public class AccountWebController {
public static final String OAUTH_2_AUTHORIZATION = "/oauth2/authorization/"; public static final String OAUTH_2_AUTHORIZATION = "/oauth2/authorization/";
private final ApplicationProperties applicationProperties; private final ApplicationProperties applicationProperties;
private final SessionPersistentRegistry sessionPersistentRegistry; private final CustomHttpSessionListener customHttpSessionListener;
// Assuming you have a repository for user operations // Assuming you have a repository for user operations
private final UserRepository userRepository; private final UserRepository userRepository;
private final boolean loginEnabledValue; private final boolean loginEnabledValue;
@ -62,15 +62,15 @@ public class AccountWebController {
public AccountWebController( public AccountWebController(
ApplicationProperties applicationProperties, ApplicationProperties applicationProperties,
SessionPersistentRegistry sessionPersistentRegistry,
UserRepository userRepository, UserRepository userRepository,
@Qualifier("loginEnabled") boolean loginEnabledValue, @Qualifier("loginEnabled") boolean loginEnabledValue,
@Qualifier("runningEE") boolean runningEE) { @Qualifier("runningEE") boolean runningEE,
CustomHttpSessionListener customHttpSessionListener) {
this.applicationProperties = applicationProperties; this.applicationProperties = applicationProperties;
this.sessionPersistentRegistry = sessionPersistentRegistry;
this.userRepository = userRepository; this.userRepository = userRepository;
this.loginEnabledValue = loginEnabledValue; this.loginEnabledValue = loginEnabledValue;
this.runningEE = runningEE; this.runningEE = runningEE;
this.customHttpSessionListener = customHttpSessionListener;
} }
@GetMapping("/login") @GetMapping("/login")
@ -224,9 +224,9 @@ public class AccountWebController {
Map<String, Integer> userActiveSessions = new HashMap<>(); Map<String, Integer> userActiveSessions = new HashMap<>();
int activeUsers = 0; int activeUsers = 0;
int disabledUsers = 0; int disabledUsers = 0;
int maxSessions = sessionPersistentRegistry.getMaxSessions(); int maxSessions = customHttpSessionListener.getMaxApplicationSessions();
int maxUserSessions = sessionPersistentRegistry.getMaxUserSessions(); int maxUserSessions = customHttpSessionListener.getMaxUserSessions();
int sessionCount = sessionPersistentRegistry.getAllSessionsNotExpired().size(); int sessionCount = customHttpSessionListener.getAllNonExpiredSessions().size();
while (iterator.hasNext()) { while (iterator.hasNext()) {
User user = iterator.next(); User user = iterator.next();
if (user != null) { if (user != null) {
@ -239,13 +239,13 @@ public class AccountWebController {
} }
} }
// Determine the user's session status and last request time // Determine the user's session status and last request time
int maxInactiveInterval = sessionPersistentRegistry.getMaxInactiveInterval(); int maxInactiveInterval = customHttpSessionListener.getMaxInactiveInterval();
boolean hasActiveSession = false; boolean hasActiveSession = false;
Date lastRequest; Date lastRequest;
Optional<SessionEntity> latestSession = Optional<SessionsModelInterface> latestSession =
sessionPersistentRegistry.findLatestSession(user.getUsername()); customHttpSessionListener.findLatestSession(user.getUsername());
if (latestSession.isPresent()) { if (latestSession.isPresent()) {
SessionEntity sessionEntity = latestSession.get(); SessionEntity sessionEntity = (SessionEntity) latestSession.get();
Date lastAccessedTime = sessionEntity.getLastRequest(); Date lastAccessedTime = sessionEntity.getLastRequest();
Instant now = Instant.now(); Instant now = Instant.now();
// Calculate session expiration and update session status accordingly // Calculate session expiration and update session status accordingly
@ -254,7 +254,7 @@ public class AccountWebController {
.toInstant() .toInstant()
.plus(maxInactiveInterval, ChronoUnit.SECONDS); .plus(maxInactiveInterval, ChronoUnit.SECONDS);
if (now.isAfter(expirationTime)) { if (now.isAfter(expirationTime)) {
sessionPersistentRegistry.expireSession(sessionEntity.getSessionId()); customHttpSessionListener.expireSession(sessionEntity.getSessionId());
} else { } else {
hasActiveSession = !sessionEntity.isExpired(); hasActiveSession = !sessionEntity.isExpired();
} }
@ -271,8 +271,8 @@ public class AccountWebController {
if (!user.isEnabled()) { if (!user.isEnabled()) {
disabledUsers++; disabledUsers++;
} }
List<SessionInformation> sessionInformations = List<SessionsModelInterface> sessionInformations =
sessionPersistentRegistry.getAllSessions(user.getUsername(), false); customHttpSessionListener.getAllSessions(user.getUsername(), false);
userActiveSessions.put(user.getUsername(), sessionInformations.size()); userActiveSessions.put(user.getUsername(), sessionInformations.size());
} }
} }
@ -348,7 +348,7 @@ public class AccountWebController {
model.addAttribute("maxSessions", maxSessions); model.addAttribute("maxSessions", maxSessions);
model.addAttribute("maxUserSessions", maxUserSessions); model.addAttribute("maxUserSessions", maxUserSessions);
model.addAttribute("sessionCount", sessionCount); model.addAttribute("sessionCount", sessionCount);
model.addAttribute("maxPaidUsers", applicationProperties.getPremium().getMaxUsers()); model.addAttribute("maxPaidUsers", customHttpSessionListener.getMaxUsers());
return "adminSettings"; return "adminSettings";
} }

View File

@ -37,10 +37,10 @@
<!-- User Settings Title --> <!-- User Settings Title -->
<div <div
style="background: var(--md-sys-color-outline-variant);padding: .8rem; margin: 10px 0; border-radius: 2rem; text-align: center;"> style="background: var(--md-sys-color-outline-variant);padding: .8rem; margin: 10px 0; border-radius: 2rem; text-align: center;">
<a href="#" th:data-bs-toggle="${@runningProOrHigher && totalUsers >= maxPaidUsers} ? null : 'modal'" <a href="#" th:data-bs-toggle="${totalUsers >= maxPaidUsers} ? null : 'modal'"
th:data-bs-target="${@runningProOrHigher && totalUsers >= maxPaidUsers} ? null : '#addUserModal'" th:data-bs-target="${@runningProOrHigher && totalUsers >= maxPaidUsers} ? null : '#addUserModal'"
th:class="${@runningProOrHigher && totalUsers >= maxPaidUsers} ? 'btn btn-danger' : 'btn btn-outline-success'" th:class="${totalUsers >= maxPaidUsers} ? 'btn btn-danger' : 'btn btn-outline-success'"
th:title="${@runningProOrHigher && totalUsers >= maxPaidUsers} ? #{adminUserSettings.maxUsersReached} : #{adminUserSettings.addUser}"> th:title="${totalUsers >= maxPaidUsers} ? #{adminUserSettings.maxUsersReached} : #{adminUserSettings.addUser}">
<span class="material-symbols-rounded">person_add</span> <span class="material-symbols-rounded">person_add</span>
<span th:text="#{adminUserSettings.addUser}">Add New User</span> <span th:text="#{adminUserSettings.addUser}">Add New User</span>
</a> </a>
@ -73,13 +73,9 @@
<strong style="margin-left: 20px;" th:text="#{adminUserSettings.disabledUsers}">Disabled Users:</strong> <strong style="margin-left: 20px;" th:text="#{adminUserSettings.disabledUsers}">Disabled Users:</strong>
<span th:text="${disabledUsers}"></span> <span th:text="${disabledUsers}"></span>
<th:block th:if="${@runningProOrHigher}"> <th:block>
<strong style="margin-left: 20px;" th:text="#{adminUserSettings.totalSessions}">Total Sessions:</strong> <strong style="margin-left: 20px;" th:text="#{adminUserSettings.totalSessions}">Total Sessions:</strong>
<span th:text="${sessionCount}"></span> <span th:text="${sessionCount}"></span><span th:text="' | ' + ${maxSessions}"></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:if="${!@runningEE}" th:text="' | ' + ${maxSessions}"></span>
</th:block> </th:block>
</div> </div>
</div> </div>