mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-06-03 15:52:01 +00:00
Add support for expired sessions and improve user management
This commit is contained in:
parent
d8cca66560
commit
df31733501
@ -22,6 +22,7 @@ import jakarta.servlet.FilterChain;
|
|||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import jakarta.servlet.http.HttpSession;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@ -32,6 +33,7 @@ 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;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
|
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
|
||||||
|
import stirling.software.SPDF.model.SessionEntity;
|
||||||
import stirling.software.SPDF.model.User;
|
import stirling.software.SPDF.model.User;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@ -59,26 +61,66 @@ 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 (authentication != null && authentication.isAuthenticated() && loginEnabledValue) {
|
||||||
|
Object principalTest = authentication.getPrincipal();
|
||||||
|
String username = UserUtils.getUsernameFromPrincipal(principalTest);
|
||||||
|
|
||||||
|
log.info("Principal: {}", username);
|
||||||
|
List<SessionInformation> allSessions =
|
||||||
|
sessionPersistentRegistry.getAllSessions(username, false);
|
||||||
|
|
||||||
|
HttpSession session = request.getSession(false);
|
||||||
|
if (session == null) {
|
||||||
|
session = request.getSession(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
String sessionId = request.getSession(false).getId();
|
||||||
|
|
||||||
|
log.info("allSessions: {} username: {}", allSessions.size(), username);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
SessionEntity sessionEntity = sessionPersistentRegistry.getSessionEntity(sessionId);
|
||||||
|
|
||||||
|
if (allSessions.isEmpty() || sessionEntity.isExpired()) {
|
||||||
|
log.info("No sessions found for user: {}", username);
|
||||||
|
sessionPersistentRegistry.expireSession(sessionId);
|
||||||
|
authentication.setAuthenticated(false);
|
||||||
|
SecurityContextHolder.clearContext();
|
||||||
|
response.sendRedirect(request.getContextPath() + "/login?error=expiredSession");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
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();
|
// 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()) {
|
||||||
|
@ -12,7 +12,6 @@ import org.springframework.security.core.Authentication;
|
|||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
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.core.userdetails.UsernameNotFoundException;
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
@ -380,23 +379,10 @@ public class UserService implements UserServiceInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void invalidateUserSessions(String username) {
|
public void invalidateUserSessions(String username) {
|
||||||
String usernameP = "";
|
|
||||||
|
|
||||||
for (Object principal : sessionRegistry.getAllPrincipals()) {
|
for (Object principal : sessionRegistry.getAllPrincipals()) {
|
||||||
for (SessionInformation sessionsInformation :
|
String usernameP = UserUtils.getUsernameFromPrincipal(principal);
|
||||||
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;
|
|
||||||
}
|
|
||||||
if (usernameP.equalsIgnoreCase(username)) {
|
if (usernameP.equalsIgnoreCase(username)) {
|
||||||
sessionRegistry.expireSession(sessionsInformation.getSessionId());
|
sessionRegistry.expireAllSessionsByPrincipalName(usernameP);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
||||||
|
|
||||||
|
public class UserUtils {
|
||||||
|
public static String getUsernameFromPrincipal(Object principal) {
|
||||||
|
if (principal instanceof UserDetails detailsUser) {
|
||||||
|
return detailsUser.getUsername();
|
||||||
|
} else if (principal instanceof OAuth2User oAuth2User) {
|
||||||
|
return oAuth2User.getName();
|
||||||
|
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
|
||||||
|
return saml2User.name();
|
||||||
|
} else if (principal instanceof String stringUser) {
|
||||||
|
return stringUser;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,6 @@ import java.sql.SQLException;
|
|||||||
|
|
||||||
import org.springframework.security.authentication.LockedException;
|
import org.springframework.security.authentication.LockedException;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
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.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
|
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
|
||||||
import org.springframework.security.web.savedrequest.SavedRequest;
|
import org.springframework.security.web.savedrequest.SavedRequest;
|
||||||
@ -17,6 +16,7 @@ import jakarta.servlet.http.HttpSession;
|
|||||||
|
|
||||||
import stirling.software.SPDF.config.security.LoginAttemptService;
|
import stirling.software.SPDF.config.security.LoginAttemptService;
|
||||||
import stirling.software.SPDF.config.security.UserService;
|
import stirling.software.SPDF.config.security.UserService;
|
||||||
|
import stirling.software.SPDF.config.security.UserUtils;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
||||||
import stirling.software.SPDF.model.AuthenticationType;
|
import stirling.software.SPDF.model.AuthenticationType;
|
||||||
@ -45,13 +45,7 @@ public class CustomOAuth2AuthenticationSuccessHandler
|
|||||||
throws ServletException, IOException {
|
throws ServletException, IOException {
|
||||||
|
|
||||||
Object principal = authentication.getPrincipal();
|
Object principal = authentication.getPrincipal();
|
||||||
String username = "";
|
String username = UserUtils.getUsernameFromPrincipal(principal);
|
||||||
|
|
||||||
if (principal instanceof OAuth2User oAuth2User) {
|
|
||||||
username = oAuth2User.getName();
|
|
||||||
} else if (principal instanceof UserDetails detailsUser) {
|
|
||||||
username = detailsUser.getUsername();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the saved request
|
// Get the saved request
|
||||||
HttpSession session = request.getSession(false);
|
HttpSession session = request.getSession(false);
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package stirling.software.SPDF.config.security.session;
|
package stirling.software.SPDF.config.security.session;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpSessionEvent;
|
import jakarta.servlet.http.HttpSessionEvent;
|
||||||
@ -8,20 +10,43 @@ import jakarta.servlet.http.HttpSessionListener;
|
|||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.config.security.UserUtils;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class CustomHttpSessionListener implements HttpSessionListener {
|
public class CustomHttpSessionListener implements HttpSessionListener {
|
||||||
|
|
||||||
private SessionPersistentRegistry sessionPersistentRegistry;
|
private final SessionPersistentRegistry sessionPersistentRegistry;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public CustomHttpSessionListener(SessionPersistentRegistry sessionPersistentRegistry) {
|
public CustomHttpSessionListener(SessionPersistentRegistry sessionPersistentRegistry) {
|
||||||
super();
|
super();
|
||||||
this.sessionPersistentRegistry = sessionPersistentRegistry;
|
this.sessionPersistentRegistry = sessionPersistentRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sessionCreated(HttpSessionEvent se) {}
|
public void sessionCreated(HttpSessionEvent se) {
|
||||||
|
SecurityContext securityContext = SecurityContextHolder.getContext();
|
||||||
|
if (securityContext == null) {
|
||||||
|
log.debug("Security context is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Authentication authentication = securityContext.getAuthentication();
|
||||||
|
if (authentication == null) {
|
||||||
|
log.info("Authentication is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Object principal = authentication.getPrincipal();
|
||||||
|
if (principal == null) {
|
||||||
|
log.info("Principal is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String principalName = UserUtils.getUsernameFromPrincipal(principal);
|
||||||
|
if (principalName == null || "anonymousUser".equals(principalName)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.info("Session created: {}", principalName);
|
||||||
|
sessionPersistentRegistry.registerNewSession(se.getSession().getId(), principalName);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sessionDestroyed(HttpSessionEvent se) {
|
public void sessionDestroyed(HttpSessionEvent se) {
|
||||||
|
@ -1,26 +1,31 @@
|
|||||||
package stirling.software.SPDF.config.security.session;
|
package stirling.software.SPDF.config.security.session;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.security.core.session.SessionInformation;
|
import org.springframework.security.core.session.SessionInformation;
|
||||||
import org.springframework.security.core.session.SessionRegistry;
|
import org.springframework.security.core.session.SessionRegistry;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
|
||||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import jakarta.transaction.Transactional;
|
import jakarta.transaction.Transactional;
|
||||||
|
|
||||||
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.config.security.UserUtils;
|
||||||
import stirling.software.SPDF.model.SessionEntity;
|
import stirling.software.SPDF.model.SessionEntity;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
|
@Slf4j
|
||||||
public class SessionPersistentRegistry implements SessionRegistry {
|
public class SessionPersistentRegistry implements SessionRegistry {
|
||||||
|
|
||||||
private final SessionRepository sessionRepository;
|
private final SessionRepository sessionRepository;
|
||||||
|
|
||||||
@Value("${server.servlet.session.timeout:30m}")
|
@Value("${server.servlet.session.timeout:120s}") // TODO: Change to 30m
|
||||||
private Duration defaultMaxInactiveInterval;
|
private Duration defaultMaxInactiveInterval;
|
||||||
|
|
||||||
public SessionPersistentRegistry(SessionRepository sessionRepository) {
|
public SessionPersistentRegistry(SessionRepository sessionRepository) {
|
||||||
@ -41,17 +46,7 @@ public class SessionPersistentRegistry implements SessionRegistry {
|
|||||||
public List<SessionInformation> getAllSessions(
|
public List<SessionInformation> getAllSessions(
|
||||||
Object principal, boolean includeExpiredSessions) {
|
Object principal, boolean includeExpiredSessions) {
|
||||||
List<SessionInformation> sessionInformations = new ArrayList<>();
|
List<SessionInformation> sessionInformations = new ArrayList<>();
|
||||||
String principalName = null;
|
String principalName = UserUtils.getUsernameFromPrincipal(principal);
|
||||||
|
|
||||||
if (principal instanceof UserDetails detailsUser) {
|
|
||||||
principalName = detailsUser.getUsername();
|
|
||||||
} else if (principal instanceof OAuth2User oAuth2User) {
|
|
||||||
principalName = oAuth2User.getName();
|
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
|
|
||||||
principalName = saml2User.name();
|
|
||||||
} else if (principal instanceof String stringUser) {
|
|
||||||
principalName = stringUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (principalName != null) {
|
if (principalName != null) {
|
||||||
List<SessionEntity> sessionEntities =
|
List<SessionEntity> sessionEntities =
|
||||||
@ -72,29 +67,15 @@ public class SessionPersistentRegistry implements SessionRegistry {
|
|||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public void registerNewSession(String sessionId, Object principal) {
|
public void registerNewSession(String sessionId, Object principal) {
|
||||||
String principalName = null;
|
String principalName = UserUtils.getUsernameFromPrincipal(principal);
|
||||||
|
|
||||||
if (principal instanceof UserDetails detailsUser) {
|
|
||||||
principalName = detailsUser.getUsername();
|
|
||||||
} else if (principal instanceof OAuth2User oAuth2User) {
|
|
||||||
principalName = oAuth2User.getName();
|
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
|
|
||||||
principalName = saml2User.name();
|
|
||||||
} else if (principal instanceof String stringUser) {
|
|
||||||
principalName = stringUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (principalName != null) {
|
if (principalName != null) {
|
||||||
// Clear old sessions for the principal (unsure if needed)
|
SessionEntity sessionEntity = sessionRepository.findBySessionId(sessionId);
|
||||||
// List<SessionEntity> existingSessions =
|
if (sessionEntity == null) {
|
||||||
// sessionRepository.findByPrincipalName(principalName);
|
sessionEntity = new SessionEntity();
|
||||||
// for (SessionEntity session : existingSessions) {
|
|
||||||
// session.setExpired(true);
|
|
||||||
// sessionRepository.save(session);
|
|
||||||
// }
|
|
||||||
|
|
||||||
SessionEntity sessionEntity = new SessionEntity();
|
|
||||||
sessionEntity.setSessionId(sessionId);
|
sessionEntity.setSessionId(sessionId);
|
||||||
|
log.info("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
|
||||||
sessionEntity.setExpired(false);
|
sessionEntity.setExpired(false);
|
||||||
@ -111,11 +92,12 @@ public class SessionPersistentRegistry implements SessionRegistry {
|
|||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public void refreshLastRequest(String sessionId) {
|
public void refreshLastRequest(String sessionId) {
|
||||||
Optional<SessionEntity> sessionEntityOpt = sessionRepository.findById(sessionId);
|
SessionEntity sessionEntity = sessionRepository.findBySessionId(sessionId);
|
||||||
if (sessionEntityOpt.isPresent()) {
|
if (sessionEntity != null) {
|
||||||
SessionEntity sessionEntity = sessionEntityOpt.get();
|
|
||||||
sessionEntity.setLastRequest(new Date()); // Update lastRequest to the current date
|
sessionEntity.setLastRequest(new Date()); // Update lastRequest to the current date
|
||||||
sessionRepository.save(sessionEntity);
|
sessionRepository.save(sessionEntity);
|
||||||
|
} else {
|
||||||
|
log.error("Session not found for session ID: {}", sessionId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,6 +134,15 @@ public class SessionPersistentRegistry implements SessionRegistry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mark all sessions as expired for a given principal name
|
||||||
|
public void expireAllSessionsByPrincipalName(String principalName) {
|
||||||
|
List<SessionEntity> sessionEntities = sessionRepository.findByPrincipalName(principalName);
|
||||||
|
for (SessionEntity sessionEntity : sessionEntities) {
|
||||||
|
sessionEntity.setExpired(true); // Set expired to true
|
||||||
|
sessionRepository.save(sessionEntity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get the maximum inactive interval for sessions
|
// Get the maximum inactive interval for sessions
|
||||||
public int getMaxInactiveInterval() {
|
public int getMaxInactiveInterval() {
|
||||||
return (int) defaultMaxInactiveInterval.getSeconds();
|
return (int) defaultMaxInactiveInterval.getSeconds();
|
||||||
@ -168,6 +159,15 @@ public class SessionPersistentRegistry implements SessionRegistry {
|
|||||||
sessionRepository.saveByPrincipalName(expired, lastRequest, principalName);
|
sessionRepository.saveByPrincipalName(expired, lastRequest, principalName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update session details by session ID
|
||||||
|
public void updateSessionBySessionId(String sessionId) {
|
||||||
|
SessionEntity sessionEntity = getSessionEntity(sessionId);
|
||||||
|
if (sessionEntity != null) {
|
||||||
|
sessionEntity.setLastRequest(new Date());
|
||||||
|
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) {
|
||||||
List<SessionEntity> allSessions = sessionRepository.findByPrincipalName(principalName);
|
List<SessionEntity> allSessions = sessionRepository.findByPrincipalName(principalName);
|
||||||
@ -178,13 +178,8 @@ public class SessionPersistentRegistry implements SessionRegistry {
|
|||||||
// Sort sessions by lastRequest in descending order
|
// Sort sessions by lastRequest in descending order
|
||||||
Collections.sort(
|
Collections.sort(
|
||||||
allSessions,
|
allSessions,
|
||||||
new Comparator<SessionEntity>() {
|
(SessionEntity s1, SessionEntity s2) ->
|
||||||
@Override
|
s2.getLastRequest().compareTo(s1.getLastRequest()));
|
||||||
public int compare(SessionEntity s1, SessionEntity s2) {
|
|
||||||
// Sort by lastRequest in descending order
|
|
||||||
return s2.getLastRequest().compareTo(s1.getLastRequest());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// The first session in the list is the latest session for the given principal name
|
// The first session in the list is the latest session for the given principal name
|
||||||
return Optional.of(allSessions.get(0));
|
return Optional.of(allSessions.get(0));
|
||||||
|
@ -24,7 +24,8 @@ public interface SessionRepository extends JpaRepository<SessionEntity, String>
|
|||||||
@Modifying
|
@Modifying
|
||||||
@Transactional
|
@Transactional
|
||||||
@Query(
|
@Query(
|
||||||
"UPDATE SessionEntity s SET s.expired = :expired, s.lastRequest = :lastRequest WHERE s.principalName = :principalName")
|
"UPDATE SessionEntity s SET s.expired = :expired, s.lastRequest = :lastRequest WHERE"
|
||||||
|
+ " s.principalName = :principalName")
|
||||||
void saveByPrincipalName(
|
void saveByPrincipalName(
|
||||||
@Param("expired") boolean expired,
|
@Param("expired") boolean expired,
|
||||||
@Param("lastRequest") Date lastRequest,
|
@Param("lastRequest") Date lastRequest,
|
||||||
|
@ -6,10 +6,15 @@ import java.util.Date;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.core.session.SessionInformation;
|
import org.springframework.security.core.session.SessionInformation;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
|
@Slf4j
|
||||||
public class SessionScheduled {
|
public class SessionScheduled {
|
||||||
|
|
||||||
private final SessionPersistentRegistry sessionPersistentRegistry;
|
private final SessionPersistentRegistry sessionPersistentRegistry;
|
||||||
@ -18,10 +23,18 @@ public class SessionScheduled {
|
|||||||
this.sessionPersistentRegistry = sessionPersistentRegistry;
|
this.sessionPersistentRegistry = sessionPersistentRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Scheduled(cron = "0 0/5 * * * ?")
|
@Scheduled(cron = "0 0/1 * * * ?") // TODO: Change to 5m
|
||||||
public void expireSessions() {
|
public void expireSessions() {
|
||||||
Instant now = Instant.now();
|
Instant now = Instant.now();
|
||||||
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
for (Object principal : sessionPersistentRegistry.getAllPrincipals()) {
|
for (Object principal : sessionPersistentRegistry.getAllPrincipals()) {
|
||||||
|
if (principal == null) {
|
||||||
|
continue;
|
||||||
|
} else if (principal instanceof String stringPrincipal) {
|
||||||
|
if ("anonymousUser".equals(stringPrincipal)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
List<SessionInformation> sessionInformations =
|
List<SessionInformation> sessionInformations =
|
||||||
sessionPersistentRegistry.getAllSessions(principal, false);
|
sessionPersistentRegistry.getAllSessions(principal, false);
|
||||||
for (SessionInformation sessionInformation : sessionInformations) {
|
for (SessionInformation sessionInformation : sessionInformations) {
|
||||||
@ -30,7 +43,21 @@ public class SessionScheduled {
|
|||||||
Instant expirationTime =
|
Instant expirationTime =
|
||||||
lastRequest.toInstant().plus(maxInactiveInterval, ChronoUnit.SECONDS);
|
lastRequest.toInstant().plus(maxInactiveInterval, ChronoUnit.SECONDS);
|
||||||
if (now.isAfter(expirationTime)) {
|
if (now.isAfter(expirationTime)) {
|
||||||
|
log.info(
|
||||||
|
"SessionID: {} expiration time: {} Current time: {}",
|
||||||
|
sessionInformation.getSessionId(),
|
||||||
|
expirationTime,
|
||||||
|
now);
|
||||||
sessionPersistentRegistry.expireSession(sessionInformation.getSessionId());
|
sessionPersistentRegistry.expireSession(sessionInformation.getSessionId());
|
||||||
|
sessionInformation.expireNow();
|
||||||
|
if (authentication != null && principal.equals(authentication.getPrincipal())) {
|
||||||
|
authentication.setAuthenticated(false);
|
||||||
|
}
|
||||||
|
SecurityContextHolder.clearContext();
|
||||||
|
log.info(
|
||||||
|
"Session expired for principal: {} SessionID: {}",
|
||||||
|
principal,
|
||||||
|
sessionInformation.getSessionId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -147,6 +147,7 @@ public class AccountWebController {
|
|||||||
case "badCredentials" -> error = "login.invalid";
|
case "badCredentials" -> error = "login.invalid";
|
||||||
case "locked" -> error = "login.locked";
|
case "locked" -> error = "login.locked";
|
||||||
case "oauth2AuthenticationError" -> error = "userAlreadyExistsOAuthMessage";
|
case "oauth2AuthenticationError" -> error = "userAlreadyExistsOAuthMessage";
|
||||||
|
case "expiredSession" -> error = "expiredSessionMessage";
|
||||||
}
|
}
|
||||||
|
|
||||||
model.addAttribute("error", error);
|
model.addAttribute("error", error);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user