mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-05-23 02:12:01 +00:00
clean up
This commit is contained in:
parent
762571f42b
commit
fe4d2823aa
@ -2,7 +2,6 @@ package stirling.software.SPDF.config;
|
|||||||
|
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.servlet.HandlerInterceptor;
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
|
|
||||||
@ -20,15 +19,11 @@ public class EndpointInterceptor implements HandlerInterceptor {
|
|||||||
|
|
||||||
private final EndpointConfiguration endpointConfiguration;
|
private final EndpointConfiguration endpointConfiguration;
|
||||||
private final SessionsInterface sessionsInterface;
|
private final SessionsInterface sessionsInterface;
|
||||||
private boolean loginEnabled = true;
|
|
||||||
|
|
||||||
public EndpointInterceptor(
|
public EndpointInterceptor(
|
||||||
EndpointConfiguration endpointConfiguration,
|
EndpointConfiguration endpointConfiguration, SessionsInterface sessionsInterface) {
|
||||||
SessionsInterface sessionsInterface,
|
|
||||||
@Qualifier("loginEnabled") boolean loginEnabled) {
|
|
||||||
this.endpointConfiguration = endpointConfiguration;
|
this.endpointConfiguration = endpointConfiguration;
|
||||||
this.sessionsInterface = sessionsInterface;
|
this.sessionsInterface = sessionsInterface;
|
||||||
this.loginEnabled = loginEnabled;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -44,6 +39,7 @@ public class EndpointInterceptor implements HandlerInterceptor {
|
|||||||
|
|
||||||
Principal principal = request.getUserPrincipal();
|
Principal principal = request.getUserPrincipal();
|
||||||
|
|
||||||
|
// allowlist for public or static routes
|
||||||
if ("/".equals(request.getRequestURI())
|
if ("/".equals(request.getRequestURI())
|
||||||
|| "/login".equals(request.getRequestURI())
|
|| "/login".equals(request.getRequestURI())
|
||||||
|| "/home".equals(request.getRequestURI())
|
|| "/home".equals(request.getRequestURI())
|
||||||
@ -70,7 +66,6 @@ public class EndpointInterceptor implements HandlerInterceptor {
|
|||||||
|
|
||||||
final String currentPrincipal = principal.getName();
|
final String currentPrincipal = principal.getName();
|
||||||
|
|
||||||
// Zähle alle nicht abgelaufenen Sessions des aktuellen Benutzers.
|
|
||||||
long userSessions =
|
long userSessions =
|
||||||
sessionsInterface.getAllSessions().stream()
|
sessionsInterface.getAllSessions().stream()
|
||||||
.filter(
|
.filter(
|
||||||
@ -80,25 +75,19 @@ public class EndpointInterceptor implements HandlerInterceptor {
|
|||||||
s.getPrincipalName()))
|
s.getPrincipalName()))
|
||||||
.count();
|
.count();
|
||||||
|
|
||||||
// Zähle alle nicht abgelaufenen Sessions in der Anwendung.
|
|
||||||
long totalSessions =
|
long totalSessions =
|
||||||
sessionsInterface.getAllSessions().stream()
|
sessionsInterface.getAllSessions().stream()
|
||||||
.filter(s -> !s.isExpired())
|
.filter(s -> !s.isExpired())
|
||||||
.count();
|
.count();
|
||||||
|
|
||||||
log.info(
|
log.debug(
|
||||||
"Aktive Sessions für {}: {} (max: {}) | Gesamt: {} (max: {})",
|
"Active sessions for {}: {} (max: {}) | Total: {} (max: {})",
|
||||||
currentPrincipal,
|
currentPrincipal,
|
||||||
userSessions,
|
userSessions,
|
||||||
sessionsInterface.getMaxUserSessions(),
|
sessionsInterface.getMaxUserSessions(),
|
||||||
totalSessions,
|
totalSessions,
|
||||||
sessionsInterface.getMaxApplicationSessions());
|
sessionsInterface.getMaxApplicationSessions());
|
||||||
|
|
||||||
// Prüfe die Grenzen:
|
|
||||||
// Falls entweder die Benutzersessions oder die Anwendungssessions das Limit
|
|
||||||
// erreicht haben
|
|
||||||
// und die aktuelle Session noch NICHT registriert ist, dann wird ein Fehler
|
|
||||||
// zurückgegeben.
|
|
||||||
boolean isCurrentSessionRegistered =
|
boolean isCurrentSessionRegistered =
|
||||||
sessionsInterface.getAllSessions().stream()
|
sessionsInterface.getAllSessions().stream()
|
||||||
.filter(s -> !s.isExpired())
|
.filter(s -> !s.isExpired())
|
||||||
@ -114,8 +103,40 @@ public class EndpointInterceptor implements HandlerInterceptor {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wenn die Session noch nicht registriert ist, registriere sie; andernfalls update
|
// If session is not registered yet, register it; otherwise, update the last request
|
||||||
// den Last-Request.
|
// timestamp.
|
||||||
|
if (!isCurrentSessionRegistered) {
|
||||||
|
log.info("Register session: {}", sessionId);
|
||||||
|
sessionsInterface.registerSession(finalSession);
|
||||||
|
} else {
|
||||||
|
log.info("Update session last request: {}", sessionId);
|
||||||
|
sessionsInterface.updateSessionLastRequest(sessionId);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else if (principal == null) {
|
||||||
|
if (session == null) {
|
||||||
|
session = request.getSession(true);
|
||||||
|
}
|
||||||
|
final HttpSession finalSession = session;
|
||||||
|
String sessionId = finalSession.getId();
|
||||||
|
|
||||||
|
long totalSessions =
|
||||||
|
sessionsInterface.getAllSessions().stream()
|
||||||
|
.filter(s -> !s.isExpired())
|
||||||
|
.count();
|
||||||
|
boolean isCurrentSessionRegistered =
|
||||||
|
sessionsInterface.getAllSessions().stream()
|
||||||
|
.filter(s -> !s.isExpired())
|
||||||
|
.anyMatch(s -> s.getSessionId().equals(sessionId));
|
||||||
|
|
||||||
|
if (totalSessions >= sessionsInterface.getMaxApplicationSessions()
|
||||||
|
&& !isCurrentSessionRegistered) {
|
||||||
|
response.sendError(
|
||||||
|
HttpServletResponse.SC_UNAUTHORIZED,
|
||||||
|
"Max sessions reached for this user. To continue on this device, please"
|
||||||
|
+ " close your session in another browser.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (!isCurrentSessionRegistered) {
|
if (!isCurrentSessionRegistered) {
|
||||||
log.info("Register session: {}", sessionId);
|
log.info("Register session: {}", sessionId);
|
||||||
sessionsInterface.registerSession(finalSession);
|
sessionsInterface.registerSession(finalSession);
|
||||||
@ -128,6 +149,7 @@ public class EndpointInterceptor implements HandlerInterceptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String requestURI = request.getRequestURI();
|
String requestURI = request.getRequestURI();
|
||||||
|
// Check if endpoint is enabled in config
|
||||||
if (!endpointConfiguration.isEndpointEnabled(requestURI)) {
|
if (!endpointConfiguration.isEndpointEnabled(requestURI)) {
|
||||||
response.sendError(HttpServletResponse.SC_FORBIDDEN, "This endpoint is disabled");
|
response.sendError(HttpServletResponse.SC_FORBIDDEN, "This endpoint is disabled");
|
||||||
return false;
|
return false;
|
||||||
|
@ -5,13 +5,16 @@ import java.util.Date;
|
|||||||
import jakarta.servlet.http.HttpSession;
|
import jakarta.servlet.http.HttpSession;
|
||||||
|
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
|
|
||||||
import stirling.software.SPDF.config.interfaces.SessionsModelInterface;
|
import stirling.software.SPDF.config.interfaces.SessionsModelInterface;
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
@ToString(exclude = "session")
|
@ToString(exclude = "session") // exclude session from toString to avoid verbose output or sensitive
|
||||||
|
// data
|
||||||
|
@AllArgsConstructor
|
||||||
public class AnonymusSessionInfo implements SessionsModelInterface {
|
public class AnonymusSessionInfo implements SessionsModelInterface {
|
||||||
private static final String principalName = "anonymousUser";
|
private static final String principalName = "anonymousUser";
|
||||||
private HttpSession session;
|
private HttpSession session;
|
||||||
@ -22,14 +25,6 @@ public class AnonymusSessionInfo implements SessionsModelInterface {
|
|||||||
private Date lastRequest;
|
private Date lastRequest;
|
||||||
private Boolean expired;
|
private Boolean expired;
|
||||||
|
|
||||||
public AnonymusSessionInfo(
|
|
||||||
HttpSession session, Date createdAt, Date lastRequest, Boolean expired) {
|
|
||||||
this.session = session;
|
|
||||||
this.createdAt = createdAt;
|
|
||||||
this.lastRequest = lastRequest;
|
|
||||||
this.expired = expired;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpSession getSession() {
|
public HttpSession getSession() {
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import java.time.Duration;
|
|||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
@ -28,7 +27,7 @@ public class AnonymusSessionRegistry implements HttpSessionListener, SessionsInt
|
|||||||
@Value("${server.servlet.session.timeout:120s}") // TODO: Change to 30m
|
@Value("${server.servlet.session.timeout:120s}") // TODO: Change to 30m
|
||||||
private Duration defaultMaxInactiveInterval;
|
private Duration defaultMaxInactiveInterval;
|
||||||
|
|
||||||
// Map zur Speicherung der Sessions inkl. Timestamp
|
// Map for storing sessions including timestamp
|
||||||
private static final Map<String, SessionsModelInterface> sessions = new ConcurrentHashMap<>();
|
private static final Map<String, SessionsModelInterface> sessions = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -42,7 +41,7 @@ public class AnonymusSessionRegistry implements HttpSessionListener, SessionsInt
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Speichern des Erstellungszeitpunkts
|
// Save creation timestamp
|
||||||
Date creationTime = new Date();
|
Date creationTime = new Date();
|
||||||
|
|
||||||
int allNonExpiredSessions = getAllNonExpiredSessions().size();
|
int allNonExpiredSessions = getAllNonExpiredSessions().size();
|
||||||
@ -78,11 +77,11 @@ public class AnonymusSessionRegistry implements HttpSessionListener, SessionsInt
|
|||||||
if (now.isAfter(expirationTime)) {
|
if (now.isAfter(expirationTime)) {
|
||||||
sessionsInfo.setExpired(true);
|
sessionsInfo.setExpired(true);
|
||||||
session.invalidate();
|
session.invalidate();
|
||||||
log.info("Session {} wurde Expired=TRUE", session.getId());
|
log.debug("Session {} expired=TRUE", session.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make a session as expired
|
// Mark a single session as expired
|
||||||
public void expireSession(String sessionId) {
|
public void expireSession(String sessionId) {
|
||||||
if (sessions.containsKey(sessionId)) {
|
if (sessions.containsKey(sessionId)) {
|
||||||
AnonymusSessionInfo sessionInfo = (AnonymusSessionInfo) sessions.get(sessionId);
|
AnonymusSessionInfo sessionInfo = (AnonymusSessionInfo) sessions.get(sessionId);
|
||||||
@ -90,12 +89,12 @@ public class AnonymusSessionRegistry implements HttpSessionListener, SessionsInt
|
|||||||
try {
|
try {
|
||||||
sessionInfo.getSession().invalidate();
|
sessionInfo.getSession().invalidate();
|
||||||
} catch (IllegalStateException e) {
|
} catch (IllegalStateException e) {
|
||||||
log.info("Session {} ist bereits invalidiert", sessionInfo.getSession().getId());
|
log.debug("Session {} already invalidated", sessionInfo.getSession().getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make all sessions as expired
|
// Mark all sessions as expired
|
||||||
public void expireAllSessions() {
|
public void expireAllSessions() {
|
||||||
sessions.values()
|
sessions.values()
|
||||||
.forEach(
|
.forEach(
|
||||||
@ -106,12 +105,12 @@ public class AnonymusSessionRegistry implements HttpSessionListener, SessionsInt
|
|||||||
try {
|
try {
|
||||||
session.invalidate();
|
session.invalidate();
|
||||||
} catch (IllegalStateException e) {
|
} catch (IllegalStateException e) {
|
||||||
log.info("Session {} ist bereits invalidiert", session.getId());
|
log.debug("Session {} already invalidated", session.getId());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark all sessions as expired by username
|
// Expire all sessions by username
|
||||||
public void expireAllSessionsByUsername(String username) {
|
public void expireAllSessionsByUsername(String username) {
|
||||||
sessions.values().stream()
|
sessions.values().stream()
|
||||||
.filter(
|
.filter(
|
||||||
@ -127,27 +126,11 @@ public class AnonymusSessionRegistry implements HttpSessionListener, SessionsInt
|
|||||||
try {
|
try {
|
||||||
session.invalidate();
|
session.invalidate();
|
||||||
} catch (IllegalStateException e) {
|
} catch (IllegalStateException e) {
|
||||||
log.info("Session {} ist bereits invalidiert", session.getId());
|
log.debug("Session {} already invalidated", session.getId());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSessionValid(String sessionId) {
|
|
||||||
boolean exists = sessions.containsKey(sessionId);
|
|
||||||
boolean expired = exists ? sessions.get(sessionId).isExpired() : false;
|
|
||||||
return exists && !expired;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isOldestNonExpiredSession(String sessionId) {
|
|
||||||
Collection<SessionsModelInterface> nonExpiredSessions = getAllNonExpiredSessions();
|
|
||||||
return nonExpiredSessions.stream()
|
|
||||||
.min(Comparator.comparing(SessionsModelInterface::getLastRequest))
|
|
||||||
.map(oldest -> oldest.getSessionId().equals(sessionId))
|
|
||||||
.orElse(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateSessionLastRequest(String sessionId) {
|
public void updateSessionLastRequest(String sessionId) {
|
||||||
if (sessions.containsKey(sessionId)) {
|
if (sessions.containsKey(sessionId)) {
|
||||||
@ -174,21 +157,13 @@ public class AnonymusSessionRegistry implements HttpSessionListener, SessionsInt
|
|||||||
sessions.clear();
|
sessions.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<SessionsModelInterface> getAllNonExpiredSessionsBySessionId(
|
|
||||||
String sessionId) {
|
|
||||||
return sessions.values().stream()
|
|
||||||
.filter(info -> !info.isExpired() && info.getSessionId().equals(sessionId))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerSession(HttpSession session) {
|
public void registerSession(HttpSession session) {
|
||||||
if (!sessions.containsKey(session.getId())) {
|
if (!sessions.containsKey(session.getId())) {
|
||||||
AnonymusSessionInfo sessionInfo =
|
AnonymusSessionInfo sessionInfo =
|
||||||
new AnonymusSessionInfo(session, new Date(), new Date(), false);
|
new AnonymusSessionInfo(session, new Date(), new Date(), false);
|
||||||
sessions.put(session.getId(), sessionInfo);
|
sessions.put(session.getId(), sessionInfo);
|
||||||
log.info("Session {} wurde registriert", session.getId());
|
log.debug("Session {} registered", session.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,16 +4,16 @@ import java.time.Duration;
|
|||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@Component
|
||||||
public class AnonymusSessionService {
|
public class AnonymusSessionService {
|
||||||
|
|
||||||
@Autowired private AnonymusSessionRegistry sessionRegistry;
|
@Autowired private AnonymusSessionRegistry sessionRegistry;
|
||||||
@ -21,27 +21,25 @@ public class AnonymusSessionService {
|
|||||||
@Value("${server.servlet.session.timeout:120s}") // TODO: Change to 30m
|
@Value("${server.servlet.session.timeout:120s}") // TODO: Change to 30m
|
||||||
private Duration defaultMaxInactiveInterval;
|
private Duration defaultMaxInactiveInterval;
|
||||||
|
|
||||||
|
// Runs every minute to expire inactive sessions
|
||||||
@Scheduled(cron = "0 0/1 * * * ?")
|
@Scheduled(cron = "0 0/1 * * * ?")
|
||||||
public void expireSessions() {
|
public void expireSessions() {
|
||||||
Instant now = Instant.now();
|
Instant now = Instant.now();
|
||||||
List<AnonymusSessionInfo> allNonExpiredSessions =
|
sessionRegistry.getAllSessions().stream()
|
||||||
sessionRegistry.getAllNonExpiredSessions().stream()
|
.filter(session -> !session.isExpired())
|
||||||
.map(s -> (AnonymusSessionInfo) s)
|
.forEach(
|
||||||
.collect(Collectors.toList());
|
session -> {
|
||||||
for (AnonymusSessionInfo sessionInformation : allNonExpiredSessions) {
|
Date lastRequest = session.getLastRequest();
|
||||||
Date lastRequest = sessionInformation.getLastRequest();
|
int maxInactiveInterval = (int) defaultMaxInactiveInterval.getSeconds();
|
||||||
int maxInactiveInterval = (int) defaultMaxInactiveInterval.getSeconds();
|
Instant expirationTime =
|
||||||
Instant expirationTime =
|
lastRequest
|
||||||
lastRequest.toInstant().plus(maxInactiveInterval, ChronoUnit.SECONDS);
|
.toInstant()
|
||||||
|
.plus(maxInactiveInterval, ChronoUnit.SECONDS);
|
||||||
|
|
||||||
if (now.isAfter(expirationTime)) {
|
if (now.isAfter(expirationTime)) {
|
||||||
log.info(
|
log.debug("Session expiration triggered");
|
||||||
"SessionID: {} expiration time: {} Current time: {}",
|
sessionRegistry.expireSession(session.getSessionId());
|
||||||
sessionInformation.getSession().getId(),
|
}
|
||||||
expirationTime,
|
});
|
||||||
now);
|
|
||||||
sessionInformation.setExpired(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,10 +19,11 @@ public class AnonymusSessionStatusController {
|
|||||||
public ResponseEntity<String> getSessionStatus(HttpServletRequest request) {
|
public ResponseEntity<String> getSessionStatus(HttpServletRequest request) {
|
||||||
HttpSession session = request.getSession(false);
|
HttpSession session = request.getSession(false);
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
|
// No session found
|
||||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("No session found");
|
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("No session found");
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isActivSesssion =
|
boolean isActiveSession =
|
||||||
sessionRegistry.getAllSessions().stream()
|
sessionRegistry.getAllSessions().stream()
|
||||||
.filter(s -> s.getSessionId().equals(session.getId()))
|
.filter(s -> s.getSessionId().equals(session.getId()))
|
||||||
.anyMatch(s -> !s.isExpired());
|
.anyMatch(s -> !s.isExpired());
|
||||||
@ -33,12 +34,17 @@ public class AnonymusSessionStatusController {
|
|||||||
long userSessions = sessionCount;
|
long userSessions = sessionCount;
|
||||||
int maxUserSessions = sessionRegistry.getMaxUserSessions();
|
int maxUserSessions = sessionRegistry.getMaxUserSessions();
|
||||||
|
|
||||||
if (userSessions >= maxUserSessions && !isActivSesssion) {
|
// Session invalid or expired
|
||||||
|
if (userSessions >= maxUserSessions && !isActiveSession) {
|
||||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
|
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
|
||||||
.body("Session ungültig oder abgelaufen");
|
.body("Session invalid or expired");
|
||||||
} else if (session.getId() != null && isActivSesssion) {
|
}
|
||||||
return ResponseEntity.ok("Session gültig: " + session.getId());
|
// Valid session
|
||||||
} else {
|
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");
|
return ResponseEntity.ok("User has " + userSessions + " sessions");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -47,6 +53,7 @@ public class AnonymusSessionStatusController {
|
|||||||
public ResponseEntity<String> expireSession(HttpServletRequest request) {
|
public ResponseEntity<String> expireSession(HttpServletRequest request) {
|
||||||
HttpSession session = request.getSession(false);
|
HttpSession session = request.getSession(false);
|
||||||
if (session != null) {
|
if (session != null) {
|
||||||
|
// Invalidate current session
|
||||||
sessionRegistry.expireSession(session.getId());
|
sessionRegistry.expireSession(session.getId());
|
||||||
return ResponseEntity.ok("Session invalidated");
|
return ResponseEntity.ok("Session invalidated");
|
||||||
} else {
|
} else {
|
||||||
@ -56,12 +63,14 @@ public class AnonymusSessionStatusController {
|
|||||||
|
|
||||||
@GetMapping("/session/expire/all")
|
@GetMapping("/session/expire/all")
|
||||||
public ResponseEntity<String> expireAllSessions() {
|
public ResponseEntity<String> expireAllSessions() {
|
||||||
|
// Invalidate all sessions
|
||||||
sessionRegistry.expireAllSessions();
|
sessionRegistry.expireAllSessions();
|
||||||
return ResponseEntity.ok("All sessions invalidated");
|
return ResponseEntity.ok("All sessions invalidated");
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/session/expire/{username}")
|
@GetMapping("/session/expire/{username}")
|
||||||
public ResponseEntity<String> expireAllSessionsByUsername(@PathVariable String username) {
|
public ResponseEntity<String> expireAllSessionsByUsername(@PathVariable String username) {
|
||||||
|
// Invalidate all sessions for specific user
|
||||||
sessionRegistry.expireAllSessionsByUsername(username);
|
sessionRegistry.expireAllSessionsByUsername(username);
|
||||||
return ResponseEntity.ok("All sessions invalidated for user: " + username);
|
return ResponseEntity.ok("All sessions invalidated for user: " + username);
|
||||||
}
|
}
|
||||||
|
@ -6,18 +6,12 @@ import jakarta.servlet.http.HttpSession;
|
|||||||
|
|
||||||
public interface SessionsInterface {
|
public interface SessionsInterface {
|
||||||
|
|
||||||
boolean isSessionValid(String sessionId);
|
|
||||||
|
|
||||||
boolean isOldestNonExpiredSession(String sessionId);
|
|
||||||
|
|
||||||
void updateSessionLastRequest(String sessionId);
|
void updateSessionLastRequest(String sessionId);
|
||||||
|
|
||||||
Collection<SessionsModelInterface> getAllSessions();
|
Collection<SessionsModelInterface> getAllSessions();
|
||||||
|
|
||||||
Collection<SessionsModelInterface> getAllNonExpiredSessions();
|
Collection<SessionsModelInterface> getAllNonExpiredSessions();
|
||||||
|
|
||||||
Collection<SessionsModelInterface> getAllNonExpiredSessionsBySessionId(String sessionId);
|
|
||||||
|
|
||||||
void registerSession(HttpSession session);
|
void registerSession(HttpSession session);
|
||||||
|
|
||||||
void removeSession(HttpSession session);
|
void removeSession(HttpSession session);
|
||||||
|
@ -58,13 +58,26 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||||
throws ServletException, IOException {
|
throws ServletException, IOException {
|
||||||
|
|
||||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
|
||||||
if (!loginEnabledValue) {
|
if (!loginEnabledValue) {
|
||||||
// If login is not enabled, just pass all requests without authentication
|
// If login is not enabled, just pass all requests without authentication
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String requestURI = request.getRequestURI();
|
String requestURI = request.getRequestURI();
|
||||||
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
|
||||||
|
// Check for session expiration (unsure if needed)
|
||||||
|
// if (authentication != null && authentication.isAuthenticated()) {
|
||||||
|
// String sessionId = request.getSession().getId();
|
||||||
|
// SessionInformation sessionInfo =
|
||||||
|
// sessionPersistentRegistry.getSessionInformation(sessionId);
|
||||||
|
//
|
||||||
|
// if (sessionInfo != null && sessionInfo.isExpired()) {
|
||||||
|
// SecurityContextHolder.clearContext();
|
||||||
|
// response.sendRedirect(request.getContextPath() + "/login?expired=true");
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
// Check for API key in the request headers if no authentication exists
|
// Check for API key in the request headers if no authentication exists
|
||||||
if (authentication == null || !authentication.isAuthenticated()) {
|
if (authentication == null || !authentication.isAuthenticated()) {
|
||||||
@ -110,10 +123,10 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
response.getWriter()
|
response.getWriter()
|
||||||
.write(
|
.write(
|
||||||
"Authentication required. Please provide a X-API-KEY in request"
|
"Authentication required. Please provide a X-API-KEY in request"
|
||||||
+ " header.\n"
|
+ " header.\n"
|
||||||
+ "This is found in Settings -> Account Settings -> API Key\n"
|
+ "This is found in Settings -> Account Settings -> API Key\n"
|
||||||
+ "Alternatively you can disable authentication if this is"
|
+ "Alternatively you can disable authentication if this is"
|
||||||
+ " unexpected");
|
+ " unexpected");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,6 @@ 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.SessionEntity;
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@ -57,40 +56,11 @@ public class CustomHttpSessionListener implements HttpSessionListener, SessionsI
|
|||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<SessionsModelInterface> getAllNonExpiredSessionsBySessionId(
|
|
||||||
String sessionId) {
|
|
||||||
return sessionPersistentRegistry.getAllNonExpiredSessionsBySessionId(sessionId).stream()
|
|
||||||
.map(session -> (SessionsModelInterface) session)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<SessionsModelInterface> getAllSessions() {
|
public Collection<SessionsModelInterface> getAllSessions() {
|
||||||
return new ArrayList<>(sessionPersistentRegistry.getAllSessions());
|
return new ArrayList<>(sessionPersistentRegistry.getAllSessions());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSessionValid(String sessionId) {
|
|
||||||
List<SessionEntity> allSessions = sessionPersistentRegistry.getAllSessions();
|
|
||||||
// gib zurück ob ist expired
|
|
||||||
return allSessions.stream()
|
|
||||||
.anyMatch(
|
|
||||||
session ->
|
|
||||||
session.getSessionId().equals(sessionId) && !session.isExpired());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isOldestNonExpiredSession(String sessionId) {
|
|
||||||
log.info("isOldestNonExpiredSession for sessionId: {}", sessionId);
|
|
||||||
List<SessionEntity> nonExpiredSessions =
|
|
||||||
sessionPersistentRegistry.getAllSessionsNotExpired();
|
|
||||||
return nonExpiredSessions.stream()
|
|
||||||
.min(Comparator.comparing(SessionEntity::getLastRequest))
|
|
||||||
.map(oldest -> oldest.getSessionId().equals(sessionId))
|
|
||||||
.orElse(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateSessionLastRequest(String sessionId) {
|
public void updateSessionLastRequest(String sessionId) {
|
||||||
sessionPersistentRegistry.refreshLastRequest(sessionId);
|
sessionPersistentRegistry.refreshLastRequest(sessionId);
|
||||||
@ -121,16 +91,20 @@ public class CustomHttpSessionListener implements HttpSessionListener, SessionsI
|
|||||||
if ("anonymousUser".equals(principalName) && loginEnabled) {
|
if ("anonymousUser".equals(principalName) && loginEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int allNonExpiredSessions = getAllNonExpiredSessions().size();
|
|
||||||
|
|
||||||
allNonExpiredSessions =
|
int allNonExpiredSessions;
|
||||||
getAllSessions().stream()
|
|
||||||
.filter(s -> !s.isExpired())
|
if ("anonymousUser".equals(principalName) && !loginEnabled) {
|
||||||
.filter(s -> s.getPrincipalName().equals(principalName))
|
allNonExpiredSessions =
|
||||||
.filter(s -> "anonymousUser".equals(principalName) && !loginEnabled)
|
(int) getAllSessions().stream().filter(s -> !s.isExpired()).count();
|
||||||
.peek(s -> log.info("Session {}", s.getPrincipalName()))
|
} else {
|
||||||
.toList()
|
allNonExpiredSessions =
|
||||||
.size();
|
(int)
|
||||||
|
getAllSessions().stream()
|
||||||
|
.filter(s -> !s.isExpired())
|
||||||
|
.filter(s -> s.getPrincipalName().equals(principalName))
|
||||||
|
.count();
|
||||||
|
}
|
||||||
|
|
||||||
int all =
|
int all =
|
||||||
getAllSessions().stream()
|
getAllSessions().stream()
|
||||||
@ -138,7 +112,7 @@ public class CustomHttpSessionListener implements HttpSessionListener, SessionsI
|
|||||||
.toList()
|
.toList()
|
||||||
.size();
|
.size();
|
||||||
boolean isAnonymousUserWithoutLogin = "anonymousUser".equals(principalName) && loginEnabled;
|
boolean isAnonymousUserWithoutLogin = "anonymousUser".equals(principalName) && loginEnabled;
|
||||||
log.info(
|
log.debug(
|
||||||
"all {} allNonExpiredSessions {} {} isAnonymousUserWithoutLogin {}",
|
"all {} allNonExpiredSessions {} {} isAnonymousUserWithoutLogin {}",
|
||||||
all,
|
all,
|
||||||
allNonExpiredSessions,
|
allNonExpiredSessions,
|
||||||
@ -146,7 +120,7 @@ public class CustomHttpSessionListener implements HttpSessionListener, SessionsI
|
|||||||
isAnonymousUserWithoutLogin);
|
isAnonymousUserWithoutLogin);
|
||||||
|
|
||||||
if (allNonExpiredSessions >= getMaxApplicationSessions() && !isAnonymousUserWithoutLogin) {
|
if (allNonExpiredSessions >= getMaxApplicationSessions() && !isAnonymousUserWithoutLogin) {
|
||||||
log.info("Session {} Expired=TRUE", session.getId());
|
log.debug("Session {} Expired=TRUE", session.getId());
|
||||||
sessionPersistentRegistry.expireSession(session.getId());
|
sessionPersistentRegistry.expireSession(session.getId());
|
||||||
sessionPersistentRegistry.removeSessionInformation(se.getSession().getId());
|
sessionPersistentRegistry.removeSessionInformation(se.getSession().getId());
|
||||||
// if (allNonExpiredSessions > getMaxUserSessions()) {
|
// if (allNonExpiredSessions > getMaxUserSessions()) {
|
||||||
@ -154,12 +128,12 @@ public class CustomHttpSessionListener implements HttpSessionListener, SessionsI
|
|||||||
// }
|
// }
|
||||||
} else if (all >= getMaxUserSessions() && !isAnonymousUserWithoutLogin) {
|
} else if (all >= getMaxUserSessions() && !isAnonymousUserWithoutLogin) {
|
||||||
enforceMaxSessionsForPrincipal(principalName);
|
enforceMaxSessionsForPrincipal(principalName);
|
||||||
log.info("Session {} Expired=TRUE", principalName);
|
log.debug("Session {} Expired=TRUE", session.getId());
|
||||||
} else if (isAnonymousUserWithoutLogin) {
|
} else if (isAnonymousUserWithoutLogin) {
|
||||||
sessionPersistentRegistry.expireSession(session.getId());
|
sessionPersistentRegistry.expireSession(session.getId());
|
||||||
sessionPersistentRegistry.removeSessionInformation(se.getSession().getId());
|
sessionPersistentRegistry.removeSessionInformation(se.getSession().getId());
|
||||||
} else {
|
} else {
|
||||||
log.info("Session created: {}", principalName);
|
log.debug("Session created: {}", session.getId());
|
||||||
sessionPersistentRegistry.registerNewSession(se.getSession().getId(), principalName);
|
sessionPersistentRegistry.registerNewSession(se.getSession().getId(), principalName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -175,7 +149,7 @@ public class CustomHttpSessionListener implements HttpSessionListener, SessionsI
|
|||||||
int maxAllowed = getMaxUserSessions();
|
int maxAllowed = getMaxUserSessions();
|
||||||
if (userSessions.size() > maxAllowed) {
|
if (userSessions.size() > maxAllowed) {
|
||||||
int sessionsToRemove = userSessions.size() - maxAllowed;
|
int sessionsToRemove = userSessions.size() - maxAllowed;
|
||||||
log.info(
|
log.debug(
|
||||||
"User {} has {} active sessions, removing {} oldest session(s).",
|
"User {} has {} active sessions, removing {} oldest session(s).",
|
||||||
principalName,
|
principalName,
|
||||||
userSessions.size(),
|
userSessions.size(),
|
||||||
@ -186,7 +160,7 @@ public class CustomHttpSessionListener implements HttpSessionListener, SessionsI
|
|||||||
// die die Session anhand der Session-ID invalidieren und entfernen.
|
// die die Session anhand der Session-ID invalidieren und entfernen.
|
||||||
sessionPersistentRegistry.expireSession(sessionModel.getSessionId());
|
sessionPersistentRegistry.expireSession(sessionModel.getSessionId());
|
||||||
sessionPersistentRegistry.removeSessionInformation(sessionModel.getSessionId());
|
sessionPersistentRegistry.removeSessionInformation(sessionModel.getSessionId());
|
||||||
log.info(
|
log.debug(
|
||||||
"Removed session {} for principal {}",
|
"Removed session {} for principal {}",
|
||||||
sessionModel.getSessionId(),
|
sessionModel.getSessionId(),
|
||||||
principalName);
|
principalName);
|
||||||
@ -216,7 +190,7 @@ public class CustomHttpSessionListener implements HttpSessionListener, SessionsI
|
|||||||
sessionPersistentRegistry.expireSession(session.getId());
|
sessionPersistentRegistry.expireSession(session.getId());
|
||||||
session.invalidate();
|
session.invalidate();
|
||||||
sessionPersistentRegistry.removeSessionInformation(se.getSession().getId());
|
sessionPersistentRegistry.removeSessionInformation(se.getSession().getId());
|
||||||
log.info("Session {} wurde Expired=TRUE", session.getId());
|
log.debug("Session {} expired=TRUE", session.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,7 +204,7 @@ public class CustomHttpSessionListener implements HttpSessionListener, SessionsI
|
|||||||
sessionPersistentRegistry.expireSession(session.getId());
|
sessionPersistentRegistry.expireSession(session.getId());
|
||||||
session.invalidate();
|
session.invalidate();
|
||||||
sessionPersistentRegistry.removeSessionInformation(session.getId());
|
sessionPersistentRegistry.removeSessionInformation(session.getId());
|
||||||
log.info("Session {} wurde Expired=TRUE", session.getId());
|
log.debug("Session {} expired=TRUE", session.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the maximum number of sessions
|
// Get the maximum number of sessions
|
||||||
|
@ -31,14 +31,20 @@ public class PreLogoutDataCaptureHandler implements LogoutHandler {
|
|||||||
if (sessionId == null) {
|
if (sessionId == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String path = request.getServletPath();
|
String path = request.getServletPath();
|
||||||
if (path == null) {
|
if (path == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only handle explicit logout requests
|
||||||
if (!"/logout".equals(path)) {
|
if (!"/logout".equals(path)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug("Session ID: {} Principal: {}", sessionId, authentication.getPrincipal());
|
log.debug("Session ID: {} Principal: {}", sessionId, authentication.getPrincipal());
|
||||||
|
|
||||||
|
// Mark the session as expired and remove its record
|
||||||
sessionPersistentRegistry.expireSession(sessionId);
|
sessionPersistentRegistry.expireSession(sessionId);
|
||||||
sessionPersistentRegistry.removeSessionInformation(sessionId);
|
sessionPersistentRegistry.removeSessionInformation(sessionId);
|
||||||
}
|
}
|
||||||
|
@ -76,11 +76,17 @@ public class SessionPersistentRegistry implements SessionRegistry {
|
|||||||
String principalName = UserUtils.getUsernameFromPrincipal(principal);
|
String principalName = UserUtils.getUsernameFromPrincipal(principal);
|
||||||
|
|
||||||
if (principalName != null) {
|
if (principalName != null) {
|
||||||
|
|
||||||
|
int sessionUserCount = getAllSessions(principalName, false).size();
|
||||||
|
|
||||||
|
if (sessionUserCount >= getMaxUserSessions()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
SessionEntity sessionEntity = sessionRepository.findBySessionId(sessionId);
|
SessionEntity sessionEntity = sessionRepository.findBySessionId(sessionId);
|
||||||
if (sessionEntity == null) {
|
if (sessionEntity == null) {
|
||||||
sessionEntity = new SessionEntity();
|
sessionEntity = new SessionEntity();
|
||||||
sessionEntity.setSessionId(sessionId);
|
sessionEntity.setSessionId(sessionId);
|
||||||
log.info("Registering new session for principal: {}", principalName);
|
log.debug("Registering new session for principal: {}", principalName);
|
||||||
}
|
}
|
||||||
sessionEntity.setPrincipalName(principalName);
|
sessionEntity.setPrincipalName(principalName);
|
||||||
sessionEntity.setLastRequest(new Date()); // Set lastRequest to the current date
|
sessionEntity.setLastRequest(new Date()); // Set lastRequest to the current date
|
||||||
@ -128,7 +134,7 @@ public class SessionPersistentRegistry implements SessionRegistry {
|
|||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
if (nonExpiredSessions.isEmpty()) {
|
if (nonExpiredSessions.isEmpty()) {
|
||||||
log.info("Keine nicht abgelaufenen Sessions für principal {} gefunden", principalName);
|
log.debug("No active sessions found for principal {}", principalName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,8 +147,8 @@ public class SessionPersistentRegistry implements SessionRegistry {
|
|||||||
SessionEntity oldestSession = oldestSessionOpt.get();
|
SessionEntity oldestSession = oldestSessionOpt.get();
|
||||||
expireSession(oldestSession.getSessionId());
|
expireSession(oldestSession.getSessionId());
|
||||||
removeSessionInformation(oldestSession.getSessionId());
|
removeSessionInformation(oldestSession.getSessionId());
|
||||||
log.info(
|
log.debug(
|
||||||
"Die älteste Session {} für principal {} wurde als expired markiert",
|
"Oldest session {} for principal {} has been marked as expired",
|
||||||
oldestSession.getSessionId(),
|
oldestSession.getSessionId(),
|
||||||
principalName);
|
principalName);
|
||||||
}
|
}
|
||||||
@ -161,11 +167,6 @@ public class SessionPersistentRegistry implements SessionRegistry {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve all non-expired sessions
|
|
||||||
public List<SessionEntity> getAllNonExpiredSessionsBySessionId(String sessionId) {
|
|
||||||
return sessionRepository.findBySessionIdAndExpired(sessionId, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve all non-expired sessions
|
// Retrieve all non-expired sessions
|
||||||
public List<SessionEntity> getAllSessionsNotExpired() {
|
public List<SessionEntity> getAllSessionsNotExpired() {
|
||||||
return sessionRepository.findByExpired(false);
|
return sessionRepository.findByExpired(false);
|
||||||
@ -183,6 +184,7 @@ public class SessionPersistentRegistry implements SessionRegistry {
|
|||||||
SessionEntity sessionEntity = sessionEntityOpt.get();
|
SessionEntity sessionEntity = sessionEntityOpt.get();
|
||||||
sessionEntity.setExpired(true); // Set expired to true
|
sessionEntity.setExpired(true); // Set expired to true
|
||||||
sessionRepository.save(sessionEntity);
|
sessionRepository.save(sessionEntity);
|
||||||
|
log.debug("Session expired: {}", sessionId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,6 +194,7 @@ public class SessionPersistentRegistry implements SessionRegistry {
|
|||||||
for (SessionEntity sessionEntity : sessionEntities) {
|
for (SessionEntity sessionEntity : sessionEntities) {
|
||||||
sessionEntity.setExpired(true); // Set expired to true
|
sessionEntity.setExpired(true); // Set expired to true
|
||||||
sessionRepository.save(sessionEntity);
|
sessionRepository.save(sessionEntity);
|
||||||
|
log.debug("Session expired: {}", sessionEntity.getSessionId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,28 +204,29 @@ public class SessionPersistentRegistry implements SessionRegistry {
|
|||||||
for (SessionEntity sessionEntity : sessionEntities) {
|
for (SessionEntity sessionEntity : sessionEntities) {
|
||||||
sessionEntity.setExpired(true); // Set expired to true
|
sessionEntity.setExpired(true); // Set expired to true
|
||||||
sessionRepository.save(sessionEntity);
|
sessionRepository.save(sessionEntity);
|
||||||
|
log.debug("Session expired: {}", sessionEntity.getSessionId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark all sessions as expired for a given principal name
|
// Mark all sessions as expired for a given principal name
|
||||||
public void expireAllSessionsByPrincipalName(String principalName) {
|
public void expireAllSessionsByPrincipalName(String principalName) {
|
||||||
List<SessionEntity> sessionEntities = sessionRepository.findByPrincipalName(principalName);
|
List<SessionEntity> sessionEntities = sessionRepository.findByPrincipalName(principalName);
|
||||||
log.info("Session entities: {}", sessionEntities.size());
|
log.debug("Session entities: {}", sessionEntities.size());
|
||||||
for (SessionEntity sessionEntity : sessionEntities) {
|
for (SessionEntity sessionEntity : sessionEntities) {
|
||||||
log.info(
|
log.debug(
|
||||||
"Session expired: {} {} {}",
|
"Session expired: {} {} {}",
|
||||||
sessionEntity.getPrincipalName(),
|
sessionEntity.getPrincipalName(),
|
||||||
sessionEntity.isExpired(),
|
sessionEntity.isExpired(),
|
||||||
sessionEntity.getSessionId());
|
sessionEntity.getSessionId());
|
||||||
sessionEntity.setExpired(true); // Set expired to true
|
sessionEntity.setExpired(true); // Set expired to true
|
||||||
removeSessionInformation(sessionEntity.getSessionId());
|
removeSessionInformation(sessionEntity.getSessionId());
|
||||||
// sessionRepository.flush();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionEntities = sessionRepository.findByPrincipalName(principalName);
|
sessionEntities = sessionRepository.findByPrincipalName(principalName);
|
||||||
log.info("Session entities: {}", sessionEntities.size());
|
log.debug("Session entities: {}", sessionEntities.size());
|
||||||
for (SessionEntity sessionEntity : sessionEntities) {
|
for (SessionEntity sessionEntity : sessionEntities) {
|
||||||
if (sessionEntity.getPrincipalName().equals(principalName)) {
|
if (sessionEntity.getPrincipalName().equals(principalName)) {
|
||||||
log.info("Session expired: {}", sessionEntity.getSessionId());
|
log.debug("Session expired: {}", sessionEntity.getSessionId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -238,19 +242,19 @@ public class SessionPersistentRegistry implements SessionRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update session details by principal name
|
// Update session details by principal name
|
||||||
public void updateSessionByPrincipalName(
|
// public void updateSessionByPrincipalName(
|
||||||
String principalName, boolean expired, Date lastRequest) {
|
// String principalName, boolean expired, Date lastRequest) {
|
||||||
sessionRepository.saveByPrincipalName(expired, lastRequest, principalName);
|
// sessionRepository.saveByPrincipalName(expired, lastRequest, principalName);
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Update session details by session ID
|
// Update session details by session ID
|
||||||
public void updateSessionBySessionId(String sessionId) {
|
// public void updateSessionBySessionId(String sessionId) {
|
||||||
SessionEntity sessionEntity = getSessionEntity(sessionId);
|
// SessionEntity sessionEntity = getSessionEntity(sessionId);
|
||||||
if (sessionEntity != null) {
|
// if (sessionEntity != null) {
|
||||||
sessionEntity.setLastRequest(new Date());
|
// sessionEntity.setLastRequest(new Date());
|
||||||
sessionRepository.save(sessionEntity);
|
// sessionRepository.save(sessionEntity);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Find the latest session for a given principal name
|
// Find the latest session for a given principal name
|
||||||
public Optional<SessionEntity> findLatestSession(String principalName) {
|
public Optional<SessionEntity> findLatestSession(String principalName) {
|
||||||
|
@ -21,8 +21,6 @@ public interface SessionRepository extends JpaRepository<SessionEntity, String>
|
|||||||
|
|
||||||
SessionEntity findBySessionId(String sessionId);
|
SessionEntity findBySessionId(String sessionId);
|
||||||
|
|
||||||
List<SessionEntity> findBySessionIdAndExpired(String sessionId, boolean expired);
|
|
||||||
|
|
||||||
void deleteByPrincipalName(String principalName);
|
void deleteByPrincipalName(String principalName);
|
||||||
|
|
||||||
@Modifying
|
@Modifying
|
||||||
|
@ -36,7 +36,7 @@ public class SessionScheduled {
|
|||||||
if (principal == null) {
|
if (principal == null) {
|
||||||
continue;
|
continue;
|
||||||
} else if (principal instanceof String stringPrincipal) {
|
} else if (principal instanceof String stringPrincipal) {
|
||||||
// Skip anonymousUser if login is enabled
|
// Expire anonymousUser sessions if login is enabled
|
||||||
if ("anonymousUser".equals(stringPrincipal) && loginEnabledValue) {
|
if ("anonymousUser".equals(stringPrincipal) && loginEnabledValue) {
|
||||||
sessionPersistentRegistry.expireAllSessionsByPrincipalName(stringPrincipal);
|
sessionPersistentRegistry.expireAllSessionsByPrincipalName(stringPrincipal);
|
||||||
continue;
|
continue;
|
||||||
@ -52,10 +52,14 @@ public class SessionScheduled {
|
|||||||
if (now.isAfter(expirationTime)) {
|
if (now.isAfter(expirationTime)) {
|
||||||
sessionPersistentRegistry.expireSession(sessionInformation.getSessionId());
|
sessionPersistentRegistry.expireSession(sessionInformation.getSessionId());
|
||||||
sessionInformation.expireNow();
|
sessionInformation.expireNow();
|
||||||
|
|
||||||
|
// Invalidate current authentication if expired session belongs to current user
|
||||||
if (authentication != null && principal.equals(authentication.getPrincipal())) {
|
if (authentication != null && principal.equals(authentication.getPrincipal())) {
|
||||||
authentication.setAuthenticated(false);
|
authentication.setAuthenticated(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
SecurityContextHolder.clearContext();
|
SecurityContextHolder.clearContext();
|
||||||
|
|
||||||
log.debug(
|
log.debug(
|
||||||
"Session expired for principal: {} SessionID: {}",
|
"Session expired for principal: {} SessionID: {}",
|
||||||
principal,
|
principal,
|
||||||
|
@ -15,15 +15,30 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpSession;
|
import jakarta.servlet.http.HttpSession;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@Slf4j
|
||||||
public class SessionStatusController {
|
public class SessionStatusController {
|
||||||
|
|
||||||
@Autowired private SessionPersistentRegistry sessionPersistentRegistry;
|
@Autowired private SessionPersistentRegistry sessionPersistentRegistry;
|
||||||
|
|
||||||
|
// 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if the session is active and valid according to user session limits
|
||||||
@GetMapping("/session/status")
|
@GetMapping("/session/status")
|
||||||
public ResponseEntity<String> getSessionStatus(HttpServletRequest request) {
|
public ResponseEntity<String> getSessionStatus(HttpServletRequest request) {
|
||||||
HttpSession session = request.getSession(false);
|
HttpSession session = request.getSession(false);
|
||||||
@ -46,20 +61,23 @@ public class SessionStatusController {
|
|||||||
int userSessions = allSessions.size();
|
int userSessions = allSessions.size();
|
||||||
int maxUserSessions = sessionPersistentRegistry.getMaxUserSessions();
|
int maxUserSessions = sessionPersistentRegistry.getMaxUserSessions();
|
||||||
|
|
||||||
|
// Check if the current session is valid or expired based on the session registry
|
||||||
if (userSessions >= maxUserSessions && !isActivSession) {
|
if (userSessions >= maxUserSessions && !isActivSession) {
|
||||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
|
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
|
||||||
.body("Session ungültig oder abgelaufen");
|
.body("Session invalid or expired");
|
||||||
} else if (session.getId() != null && isActivSession) {
|
} else if (session.getId() != null && isActivSession) {
|
||||||
return ResponseEntity.ok("Session gültig: " + session.getId());
|
return ResponseEntity.ok("Valid session: " + session.getId());
|
||||||
} else {
|
} else {
|
||||||
return ResponseEntity.ok(
|
return ResponseEntity.ok(
|
||||||
"User: " + username + " has " + userSessions + " sessions");
|
"User: " + username + " has " + userSessions + " sessions");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Session ungültig oder abgelaufen");
|
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
|
||||||
|
.body("Session invalid or expired");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Invalidates the current session
|
||||||
@GetMapping("/session/expire")
|
@GetMapping("/session/expire")
|
||||||
public ResponseEntity<String> expireSession(HttpServletRequest request) {
|
public ResponseEntity<String> expireSession(HttpServletRequest request) {
|
||||||
HttpSession session = request.getSession(false);
|
HttpSession session = request.getSession(false);
|
||||||
@ -71,12 +89,15 @@ public class SessionStatusController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Invalidates all sessions
|
||||||
@GetMapping("/session/expire/all")
|
@GetMapping("/session/expire/all")
|
||||||
public ResponseEntity<String> expireAllSessions() {
|
public ResponseEntity<String> expireAllSessions() {
|
||||||
|
log.debug("Expire all sessions");
|
||||||
sessionPersistentRegistry.expireAllSessions();
|
sessionPersistentRegistry.expireAllSessions();
|
||||||
return ResponseEntity.ok("All sessions invalidated");
|
return ResponseEntity.ok("All sessions invalidated");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Invalidates all sessions for a specific user, only if requested by the same user
|
||||||
@GetMapping("/session/expire/{username}")
|
@GetMapping("/session/expire/{username}")
|
||||||
public ResponseEntity<String> expireAllSessionsByUsername(@PathVariable String username) {
|
public ResponseEntity<String> expireAllSessionsByUsername(@PathVariable String username) {
|
||||||
SecurityContext cxt = SecurityContextHolder.getContext();
|
SecurityContext cxt = SecurityContextHolder.getContext();
|
||||||
|
@ -3,8 +3,7 @@
|
|||||||
xmlns:th="https://www.thymeleaf.org">
|
xmlns:th="https://www.thymeleaf.org">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<th:block
|
<th:block th:insert="~{fragments/common :: head(title=#{adminUserSettings.title}, header=#{adminUserSettings.header})}">
|
||||||
th:insert="~{fragments/common :: head(title=#{adminUserSettings.title}, header=#{adminUserSettings.header})}">
|
|
||||||
</th:block>
|
</th:block>
|
||||||
<style>
|
<style>
|
||||||
.active-user {
|
.active-user {
|
||||||
@ -52,16 +51,22 @@
|
|||||||
<span th:text="#{adminUserSettings.changeUserRole}">Change User's Role</span>
|
<span th:text="#{adminUserSettings.changeUserRole}">Change User's Role</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="/usage" th:if="${@runningEE}" class="btn btn-outline-success"
|
<a href="/usage" th:if="${@runningEE}" class="btn btn-outline-success" th:title="#{adminUserSettings.usage}">
|
||||||
th:title="#{adminUserSettings.usage}">
|
|
||||||
<span class="material-symbols-rounded">analytics</span>
|
<span class="material-symbols-rounded">analytics</span>
|
||||||
<span th:text="#{adminUserSettings.usage}">Usage Statistics</span>
|
<span th:text="#{adminUserSettings.usage}">Usage Statistics</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<div class="my-4">
|
||||||
|
<strong th:if="${@runningEE}" style="margin-left: 20px;"
|
||||||
|
text="#{adminUserSettings.totalUsers}">runningEE</strong>
|
||||||
|
<strong th:if="${!@runningEE}" style="margin-left: 20px;"
|
||||||
|
text="#{adminUserSettings.totalUsers}">Non-Paid</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="my-4">
|
<div class="my-4">
|
||||||
<strong style="margin-left: 20px;" th:text="#{adminUserSettings.totalUsers}">Total Users:</strong>
|
<strong style="margin-left: 20px;" th:text="#{adminUserSettings.totalUsers}">Total Users:</strong>
|
||||||
<span th:text="${totalUsers}"></span>
|
<span th:text="${totalUsers}"></span>
|
||||||
<span th:if="${@runningProOrHigher}" th:text="'/'+${maxPaidUsers}"></span>
|
<span th:if="${@runningProOrHigher}" th:text="'| ' + ${maxPaidUsers}"></span>
|
||||||
|
|
||||||
<strong style="margin-left: 20px;" th:text="#{adminUserSettings.activeUsers}">Active Users:</strong>
|
<strong style="margin-left: 20px;" th:text="#{adminUserSettings.activeUsers}">Active Users:</strong>
|
||||||
<span th:text="${activeUsers}"></span>
|
<span th:text="${activeUsers}"></span>
|
||||||
@ -69,14 +74,12 @@
|
|||||||
<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 th:if="${@runningProOrHigher}">
|
||||||
<strong style="margin-left: 20px;" th:text="#{adminUserSettings.totalSessions}">Total
|
<strong style="margin-left: 20px;" th:text="#{adminUserSettings.totalSessions}">Total Sessions:</strong>
|
||||||
Sessions:</strong>
|
|
||||||
<span th:text="${sessionCount}"></span>
|
<span th:text="${sessionCount}"></span>
|
||||||
</th:block>
|
</th:block>
|
||||||
<th:block th:if="${!@runningProOrHigher}">
|
<th:block th:if="${!@runningProOrHigher}">
|
||||||
<strong style="margin-left: 20px;" th:text="#{adminUserSettings.totalSessions}">Total
|
<strong style="margin-left: 20px;" th:text="#{adminUserSettings.totalSessions}">Total Sessions:</strong>
|
||||||
Sessions:</strong>
|
<span th:text="${sessionCount}"></span><span th:if="${!@runningEE}" th:text="' | ' + ${maxSessions}"></span>
|
||||||
<span th:text="${sessionCount}"></span>/<span th:text="${maxSessions}"></span>
|
|
||||||
</th:block>
|
</th:block>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -103,16 +106,15 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th scope="col">#</th>
|
<th scope="col">#</th>
|
||||||
<th scope="col" th:title="#{username}" th:text="#{username}">Username</th>
|
<th scope="col" th:title="#{username}" th:text="#{username}">Username</th>
|
||||||
<th scope="col" th:title="#{adminUserSettings.roles}" th:text="#{adminUserSettings.roles}">Roles
|
<th scope="col" th:title="#{adminUserSettings.roles}" th:text="#{adminUserSettings.roles}">Roles</th>
|
||||||
</th>
|
|
||||||
<th scope="col" th:title="#{adminUserSettings.authenticated}" class="text-overflow"
|
<th scope="col" th:title="#{adminUserSettings.authenticated}" class="text-overflow"
|
||||||
th:text="#{adminUserSettings.authenticated}">Authenticated</th>
|
th:text="#{adminUserSettings.authenticated}">Authenticated</th>
|
||||||
<th scope="col" th:title="#{adminUserSettings.lastRequest}" class="text-overflow"
|
<th scope="col" th:title="#{adminUserSettings.lastRequest}" class="text-overflow"
|
||||||
th:text="#{adminUserSettings.lastRequest}">Last Request</th>
|
th:text="#{adminUserSettings.lastRequest}">Last Request</th>
|
||||||
<th scope="col" th:title="#{adminUserSettings.userSessions}"
|
<th scope="col" th:title="#{adminUserSettings.userSessions}" th:text="#{adminUserSettings.userSessions}">
|
||||||
th:text="#{adminUserSettings.userSessions}">User Sessions</th>
|
User Sessions</th>
|
||||||
<th scope="col" th:title="#{adminUserSettings.actions}" th:text="#{adminUserSettings.actions}"
|
<th scope="col" th:title="#{adminUserSettings.actions}" th:text="#{adminUserSettings.actions}" colspan="2">
|
||||||
colspan="2">Actions</th>
|
Actions</th>
|
||||||
<!-- <th scope="col"></th> -->
|
<!-- <th scope="col"></th> -->
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -140,8 +142,7 @@
|
|||||||
<form th:if="${user.username != currentUsername}"
|
<form th:if="${user.username != currentUsername}"
|
||||||
th:action="@{'/api/v1/user/admin/deleteUser/' + ${user.username}}" method="post"
|
th:action="@{'/api/v1/user/admin/deleteUser/' + ${user.username}}" method="post"
|
||||||
onsubmit="return confirmDeleteUser()">
|
onsubmit="return confirmDeleteUser()">
|
||||||
<button type="submit" th:title="#{adminUserSettings.deleteUser}"
|
<button type="submit" th:title="#{adminUserSettings.deleteUser}" class="btn btn-info btn-sm"><span
|
||||||
class="btn btn-info btn-sm"><span
|
|
||||||
class="material-symbols-rounded">person_remove</span></button>
|
class="material-symbols-rounded">person_remove</span></button>
|
||||||
</form>
|
</form>
|
||||||
<a th:if="${user.username == currentUsername}" th:title="#{adminUserSettings.editOwnProfil}"
|
<a th:if="${user.username == currentUsername}" th:title="#{adminUserSettings.editOwnProfil}"
|
||||||
@ -202,16 +203,16 @@
|
|||||||
<label for="username" th:text="#{username}">Username</label>
|
<label for="username" th:text="#{username}">Username</label>
|
||||||
<select name="username" class="form-control" required>
|
<select name="username" class="form-control" required>
|
||||||
<option value="" disabled selected th:text="#{selectFillter}">-- Select --</option>
|
<option value="" disabled selected th:text="#{selectFillter}">-- Select --</option>
|
||||||
<option th:each="user : ${users}" th:if="${user.username != currentUsername}"
|
<option th:each="user : ${users}" th:if="${user.username != currentUsername}" th:value="${user.username}"
|
||||||
th:value="${user.username}" th:text="${user.username}">Username</option>
|
th:text="${user.username}">Username</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="role" th:text="#{adminUserSettings.role}">Role</label>
|
<label for="role" th:text="#{adminUserSettings.role}">Role</label>
|
||||||
<select name="role" class="form-control" required>
|
<select name="role" class="form-control" required>
|
||||||
<option value="" disabled selected th:text="#{selectFillter}">-- Select --</option>
|
<option value="" disabled selected th:text="#{selectFillter}">-- Select --</option>
|
||||||
<option th:each="roleDetail : ${roleDetails}" th:value="${roleDetail.key}"
|
<option th:each="roleDetail : ${roleDetails}" th:value="${roleDetail.key}" th:text="#{${roleDetail.value}}">
|
||||||
th:text="#{${roleDetail.value}}">Role</option>
|
Role</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -254,8 +255,8 @@
|
|||||||
<label for="role" th:text="#{adminUserSettings.role}">Role</label>
|
<label for="role" th:text="#{adminUserSettings.role}">Role</label>
|
||||||
<select name="role" class="form-control" id="role" required>
|
<select name="role" class="form-control" id="role" required>
|
||||||
<option value="" disabled selected th:text="#{selectFillter}">-- Select --</option>
|
<option value="" disabled selected th:text="#{selectFillter}">-- Select --</option>
|
||||||
<option th:each="roleDetail : ${roleDetails}" th:value="${roleDetail.key}"
|
<option th:each="roleDetail : ${roleDetails}" th:value="${roleDetail.key}" th:text="#{${roleDetail.value}}">
|
||||||
th:text="#{${roleDetail.value}}">Role</option>
|
Role</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user