Overview
Secure session management is critical for web application security. It involves creating, maintaining, and destroying user sessions securely to prevent hijacking, fixation, and other attacks.
Core Principles
- Secure Session Creation: Use strong identifiers and secure attributes
- Session Protection: Prevent hijacking and fixation attacks
- Proper Invalidation: Secure logout and timeout handling
- Data Confidentiality: Encrypt sensitive session data
- Cross-Site Request Forgery (CSRF) Protection
Basic Session Management
1. HttpSession Security Wrapper
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpServletRequest; import java.util.*; import java.security.SecureRandom; public class SecureSessionManager { private static final String SESSION_USER_KEY = "authenticatedUser"; private static final String SESSION_CREATION_TIME = "sessionCreationTime"; private static final String SESSION_LAST_ACCESS = "sessionLastAccess"; private static final String CSRF_TOKEN_KEY = "csrfToken"; private final int sessionTimeoutMinutes; private final SecureRandom secureRandom; public SecureSessionManager(int sessionTimeoutMinutes) { this.sessionTimeoutMinutes = sessionTimeoutMinutes; this.secureRandom = new SecureRandom(); } public void createSecureSession(HttpServletRequest request, User user) { HttpSession session = request.getSession(); // Invalidate any existing session to prevent session fixation session.invalidate(); // Create new session session = request.getSession(true); // Set session attributes session.setAttribute(SESSION_USER_KEY, user); session.setAttribute(SESSION_CREATION_TIME, System.currentTimeMillis()); session.setAttribute(SESSION_LAST_ACCESS, System.currentTimeMillis()); // Generate CSRF token String csrfToken = generateCsrfToken(); session.setAttribute(CSRF_TOKEN_KEY, csrfToken); // Set session timeout session.setMaxInactiveInterval(sessionTimeoutMinutes * 60); // Set secure cookie attributes programmatically (if possible) setSecureSessionCookie(request); } public boolean isValidSession(HttpServletRequest request) { HttpSession session = request.getSession(false); if (session == null) { return false; } // Check if user is authenticated User user = (User) session.getAttribute(SESSION_USER_KEY); if (user == null) { return false; } // Check session timeout if (isSessionExpired(session)) { session.invalidate(); return false; } // Update last access time session.setAttribute(SESSION_LAST_ACCESS, System.currentTimeMillis()); return true; } public void invalidateSession(HttpServletRequest request) { HttpSession session = request.getSession(false); if (session != null) { // Clear all attributes Enumeration<String> attributeNames = session.getAttributeNames(); while (attributeNames.hasMoreElements()) { session.removeAttribute(attributeNames.nextElement()); } session.invalidate(); } // Clear session cookie clearSessionCookie(request); } public User getAuthenticatedUser(HttpServletRequest request) { if (!isValidSession(request)) { return null; } HttpSession session = request.getSession(false); return (User) session.getAttribute(SESSION_USER_KEY); } public String getCsrfToken(HttpServletRequest request) { HttpSession session = request.getSession(false); if (session == null) { return null; } return (String) session.getAttribute(CSRF_TOKEN_KEY); } public boolean validateCsrfToken(HttpServletRequest request, String token) { String sessionToken = getCsrfToken(request); return sessionToken != null && sessionToken.equals(token); } private boolean isSessionExpired(HttpSession session) { Long lastAccess = (Long) session.getAttribute(SESSION_LAST_ACCESS); if (lastAccess == null) { return true; } long currentTime = System.currentTimeMillis(); long inactiveTime = currentTime - lastAccess; return inactiveTime > (sessionTimeoutMinutes * 60 * 1000); } private String generateCsrfToken() { byte[] bytes = new byte[32]; secureRandom.nextBytes(bytes); return Base64.getEncoder().encodeToString(bytes); } private void setSecureSessionCookie(HttpServletRequest request) { // This would typically be configured in web.xml or application server settings // For programmatic approach, see Servlet 3.0+ methods } private void clearSessionCookie(HttpServletRequest request) { // Implementation depends on your servlet container } public static class User { private final String username; private final Set<String> roles; private final String sessionId; public User(String username, Set<String> roles, String sessionId) { this.username = username; this.roles = Collections.unmodifiableSet(new HashSet<>(roles)); this.sessionId = sessionId; } // Getters public String getUsername() { return username; } public Set<String> getRoles() { return roles; } public String getSessionId() { return sessionId; } public boolean hasRole(String role) { return roles.contains(role); } } } 2. Session Listener for Monitoring
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener; import javax.servlet.annotation.WebListener; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; @WebListener public class SessionMonitoringListener implements HttpSessionListener { private final AtomicInteger activeSessions = new AtomicInteger(); private final Map<String, SessionInfo> sessionStore = new ConcurrentHashMap<>(); @Override public void sessionCreated(HttpSessionEvent se) { HttpSession session = se.getSession(); activeSessions.incrementAndGet(); SessionInfo sessionInfo = new SessionInfo( session.getId(), System.currentTimeMillis(), getClientInfo(se) ); sessionStore.put(session.getId(), sessionInfo); log.info("Session created: {}. Active sessions: {}", session.getId(), activeSessions.get()); } @Override public void sessionDestroyed(HttpSessionEvent se) { HttpSession session = se.getSession(); activeSessions.decrementAndGet(); sessionStore.remove(session.getId()); log.info("Session destroyed: {}. Active sessions: {}", session.getId(), activeSessions.get()); } public int getActiveSessionCount() { return activeSessions.get(); } public SessionInfo getSessionInfo(String sessionId) { return sessionStore.get(sessionId); } public void invalidateSessionsForUser(String username) { sessionStore.values().stream() .filter(info -> username.equals(info.getUsername())) .forEach(info -> { HttpSession session = getSessionById(info.getSessionId()); if (session != null) { session.invalidate(); } }); } private String getClientInfo(HttpSessionEvent se) { // Extract client IP, user-agent, etc. return "ClientInfo"; // Simplified } private HttpSession getSessionById(String sessionId) { // Implementation depends on servlet container return null; } public static class SessionInfo { private final String sessionId; private final long creationTime; private final String clientInfo; private String username; private long lastAccessTime; public SessionInfo(String sessionId, long creationTime, String clientInfo) { this.sessionId = sessionId; this.creationTime = creationTime; this.clientInfo = clientInfo; this.lastAccessTime = creationTime; } // Getters and setters public String getSessionId() { return sessionId; } public long getCreationTime() { return creationTime; } public String getClientInfo() { return clientInfo; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public long getLastAccessTime() { return lastAccessTime; } public void setLastAccessTime(long lastAccessTime) { this.lastAccessTime = lastAccessTime; } } } Advanced Session Security
1. Distributed Session Management with Redis
import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import com.fasterxml.jackson.databind.ObjectMapper; public class RedisSessionManager { private final JedisPool jedisPool; private final ObjectMapper objectMapper; private final String sessionPrefix; private final int sessionTimeoutSeconds; public RedisSessionManager(JedisPool jedisPool, String sessionPrefix, int sessionTimeoutMinutes) { this.jedisPool = jedisPool; this.objectMapper = new ObjectMapper(); this.sessionPrefix = sessionPrefix; this.sessionTimeoutSeconds = sessionTimeoutMinutes * 60; } public SessionData createSession(User user, String clientInfo) { String sessionId = generateSessionId(); SessionData sessionData = new SessionData(sessionId, user, clientInfo); try (Jedis jedis = jedisPool.getResource()) { String key = getSessionKey(sessionId); String sessionJson = objectMapper.writeValueAsString(sessionData); jedis.setex(key, sessionTimeoutSeconds, sessionJson); } catch (Exception e) { throw new SessionException("Failed to create session", e); } return sessionData; } public SessionData getSession(String sessionId) { if (sessionId == null || sessionId.trim().isEmpty()) { return null; } try (Jedis jedis = jedisPool.getResource()) { String key = getSessionKey(sessionId); String sessionJson = jedis.get(key); if (sessionJson == null) { return null; } // Refresh TTL on access jedis.expire(key, sessionTimeoutSeconds); return objectMapper.readValue(sessionJson, SessionData.class); } catch (Exception e) { throw new SessionException("Failed to retrieve session", e); } } public void updateSession(SessionData sessionData) { try (Jedis jedis = jedisPool.getResource()) { String key = getSessionKey(sessionData.getSessionId()); String sessionJson = objectMapper.writeValueAsString(sessionData); jedis.setex(key, sessionTimeoutSeconds, sessionJson); } catch (Exception e) { throw new SessionException("Failed to update session", e); } } public void invalidateSession(String sessionId) { try (Jedis jedis = jedisPool.getResource()) { String key = getSessionKey(sessionId); jedis.del(key); } catch (Exception e) { throw new SessionException("Failed to invalidate session", e); } } public void invalidateUserSessions(String username) { try (Jedis jedis = jedisPool.getResource()) { // This is a simplified implementation // In production, you might maintain a separate index of user->sessions String pattern = sessionPrefix + "*"; jedis.keys(pattern).forEach(key -> { String sessionJson = jedis.get(key); if (sessionJson != null) { try { SessionData session = objectMapper.readValue(sessionJson, SessionData.class); if (username.equals(session.getUser().getUsername())) { jedis.del(key); } } catch (Exception e) { // Log and continue } } }); } } private String generateSessionId() { SecureRandom random = new SecureRandom(); byte[] bytes = new byte[32]; random.nextBytes(bytes); return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); } private String getSessionKey(String sessionId) { return sessionPrefix + ":" + sessionId; } public static class SessionData { private String sessionId; private User user; private String clientInfo; private long creationTime; private long lastAccessTime; private Map<String, Object> attributes; public SessionData() {} public SessionData(String sessionId, User user, String clientInfo) { this.sessionId = sessionId; this.user = user; this.clientInfo = clientInfo; this.creationTime = System.currentTimeMillis(); this.lastAccessTime = this.creationTime; this.attributes = new HashMap<>(); } public void updateLastAccess() { this.lastAccessTime = System.currentTimeMillis(); } public void setAttribute(String key, Object value) { attributes.put(key, value); } public Object getAttribute(String key) { return attributes.get(key); } public void removeAttribute(String key) { attributes.remove(key); } // Getters and setters public String getSessionId() { return sessionId; } public void setSessionId(String sessionId) { this.sessionId = sessionId; } public User getUser() { return user; } public void setUser(User user) { this.user = user; } public String getClientInfo() { return clientInfo; } public void setClientInfo(String clientInfo) { this.clientInfo = clientInfo; } public long getCreationTime() { return creationTime; } public void setCreationTime(long creationTime) { this.creationTime = creationTime; } public long getLastAccessTime() { return lastAccessTime; } public void setLastAccessTime(long lastAccessTime) { this.lastAccessTime = lastAccessTime; } public Map<String, Object> getAttributes() { return attributes; } public void setAttributes(Map<String, Object> attributes) { this.attributes = attributes; } } public static class SessionException extends RuntimeException { public SessionException(String message) { super(message); } public SessionException(String message, Throwable cause) { super(message, cause); } } } 2. JWT-Based Stateless Sessions
import io.jsonwebtoken.*; import io.jsonwebtoken.security.Keys; import javax.crypto.SecretKey; import java.util.*; public class JwtSessionManager { private final SecretKey secretKey; private final long expirationMs; private final String issuer; public JwtSessionManager(String secret, long expirationMinutes, String issuer) { this.secretKey = Keys.hmacShaKeyFor(secret.getBytes()); this.expirationMs = expirationMinutes * 60 * 1000; this.issuer = issuer; } public String createToken(User user, String clientInfo) { Map<String, Object> claims = new HashMap<>(); claims.put("username", user.getUsername()); claims.put("roles", user.getRoles()); claims.put("clientInfo", clientInfo); claims.put("sessionId", generateSessionId()); Date now = new Date(); Date expiration = new Date(now.getTime() + expirationMs); return Jwts.builder() .setClaims(claims) .setIssuer(issuer) .setIssuedAt(now) .setExpiration(expiration) .setId(generateSessionId()) .signWith(secretKey, SignatureAlgorithm.HS256) .compact(); } public SessionClaims validateToken(String token) { try { Jws<Claims> jws = Jwts.parserBuilder() .setSigningKey(secretKey) .build() .parseClaimsJws(token); Claims claims = jws.getBody(); return new SessionClaims(claims); } catch (ExpiredJwtException e) { throw new SessionException("Token expired", e); } catch (JwtException e) { throw new SessionException("Invalid token", e); } } public String refreshToken(String token) { SessionClaims claims = validateToken(token); // Create new token with same claims but extended expiration Date now = new Date(); Date expiration = new Date(now.getTime() + expirationMs); return Jwts.builder() .setClaims(claims.getOriginalClaims()) .setIssuer(issuer) .setIssuedAt(now) .setExpiration(expiration) .setId(claims.getSessionId()) .signWith(secretKey, SignatureAlgorithm.HS256) .compact(); } public void invalidateToken(String token) { // With JWT, we can't invalidate individual tokens without maintaining a blacklist // This implementation would require a token blacklist in Redis/database } private String generateSessionId() { SecureRandom random = new SecureRandom(); byte[] bytes = new byte[16]; random.nextBytes(bytes); return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); } public static class SessionClaims { private final Claims claims; public SessionClaims(Claims claims) { this.claims = claims; } public String getUsername() { return claims.get("username", String.class); } @SuppressWarnings("unchecked") public Set<String> getRoles() { List<String> roles = claims.get("roles", List.class); return new HashSet<>(roles != null ? roles : Collections.emptyList()); } public String getClientInfo() { return claims.get("clientInfo", String.class); } public String getSessionId() { return claims.get("sessionId", String.class); } public Date getExpiration() { return claims.getExpiration(); } public Date getIssuedAt() { return claims.getIssuedAt(); } public Claims getOriginalClaims() { return claims; } public boolean hasRole(String role) { return getRoles().contains(role); } } public static class SessionException extends RuntimeException { public SessionException(String message) { super(message); } public SessionException(String message, Throwable cause) { super(message, cause); } } } Spring Security Integration
1. Custom Session Management Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.web.csrf.CsrfFilter; import org.springframework.security.web.session.SessionManagementFilter; @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) .maximumSessions(1) .maxSessionsPreventsLogin(true) .expiredUrl("/login?expired") ) .csrf(csrf -> csrf .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) .ignoringAntMatchers("/api/public/**") ) .addFilterBefore(new SessionValidationFilter(), SessionManagementFilter.class) .addFilterAfter(new CsrfTokenLogger(), CsrfFilter.class) .authorizeRequests(authz -> authz .antMatchers("/public/**").permitAll() .antMatchers("/admin/**").hasRole("ADMIN") .anyRequest().authenticated() ) .formLogin(form -> form .loginPage("/login") .defaultSuccessUrl("/dashboard") .failureUrl("/login?error") .permitAll() ) .logout(logout -> logout .logoutUrl("/logout") .logoutSuccessUrl("/login?logout") .invalidateHttpSession(true) .deleteCookies("JSESSIONID") .permitAll() ); } @Bean public SessionRegistry sessionRegistry() { return new SessionRegistryImpl(); } } 2. Custom Session Validation Filter
import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; public class SessionValidationFilter extends OncePerRequestFilter { private final SessionMonitoringListener sessionMonitor; public SessionValidationFilter(SessionMonitoringListener sessionMonitor) { this.sessionMonitor = sessionMonitor; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { HttpSession session = request.getSession(false); if (session != null) { // Validate session integrity if (!isValidSession(session)) { session.invalidate(); response.sendRedirect("/login?invalid"); return; } // Check for concurrent session control if (isConcurrentSessionViolation(session)) { session.invalidate(); response.sendRedirect("/login?concurrent"); return; } // Update session activity updateSessionActivity(session); } filterChain.doFilter(request, response); } private boolean isValidSession(HttpSession session) { // Check if session has required attributes Object user = session.getAttribute("authenticatedUser"); if (user == null) { return false; } // Check session timeout Long lastAccess = (Long) session.getAttribute("lastAccessTime"); if (lastAccess == null) { return false; } long currentTime = System.currentTimeMillis(); long inactiveTime = currentTime - lastAccess; long maxInactiveTime = session.getMaxInactiveInterval() * 1000L; return inactiveTime <= maxInactiveTime; } private boolean isConcurrentSessionViolation(HttpSession session) { // Implement concurrent session control logic // This would check if the user has exceeded maximum allowed sessions return false; // Simplified } private void updateSessionActivity(HttpSession session) { session.setAttribute("lastAccessTime", System.currentTimeMillis()); } } Security Headers Configuration
1. Security Headers Filter
import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class SecurityHeadersFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // Set security headers setSecurityHeaders(response); filterChain.doFilter(request, response); } private void setSecurityHeaders(HttpServletResponse response) { // Prevent clickjacking response.setHeader("X-Frame-Options", "DENY"); // Enable XSS protection response.setHeader("X-XSS-Protection", "1; mode=block"); // Prevent MIME type sniffing response.setHeader("X-Content-Type-Options", "nosniff"); // Referrer policy response.setHeader("Referrer-Policy", "strict-origin-when-cross-origin"); // Content Security Policy response.setHeader("Content-Security-Policy", "default-src 'self'; " + "script-src 'self' 'unsafe-inline' https://trusted.cdn.com; " + "style-src 'self' 'unsafe-inline'; " + "img-src 'self' data: https:; " + "connect-src 'self'; " + "font-src 'self'; " + "object-src 'none'; " + "media-src 'self'; " + "frame-src 'none'; " + "base-uri 'self';"); // Strict Transport Security (should only be set over HTTPS) if (request.isSecure()) { response.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains"); } // Feature Policy response.setHeader("Feature-Policy", "camera 'none'; " + "microphone 'none'; " + "geolocation 'none'"); } } Best Practices Implementation
1. Comprehensive Session Manager
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; import java.util.concurrent.ConcurrentHashMap; public class ComprehensiveSessionManager { private static final Logger logger = LoggerFactory.getLogger(ComprehensiveSessionManager.class); private final Map<String, SessionMetadata> activeSessions = new ConcurrentHashMap<>(); private final int maxSessionsPerUser; private final long sessionTimeoutMs; private final Set<String> invalidatedSessions = Collections.newSetFromMap(new ConcurrentHashMap<>()); public ComprehensiveSessionManager(int maxSessionsPerUser, int sessionTimeoutMinutes) { this.maxSessionsPerUser = maxSessionsPerUser; this.sessionTimeoutMs = sessionTimeoutMinutes * 60 * 1000L; // Start session cleanup thread startSessionCleanupTask(); } public SessionContext createSession(User user, String clientIp, String userAgent) { // Check concurrent session limit enforceConcurrentSessionLimit(user.getUsername()); String sessionId = generateSecureSessionId(); SessionMetadata metadata = new SessionMetadata(sessionId, user, clientIp, userAgent); activeSessions.put(sessionId, metadata); logger.info("Session created for user: {}, sessionId: {}, clientIp: {}", user.getUsername(), sessionId, clientIp); return new SessionContext(sessionId, user, metadata.getCreationTime()); } public boolean validateSession(String sessionId, String clientIp) { if (invalidatedSessions.contains(sessionId)) { return false; } SessionMetadata metadata = activeSessions.get(sessionId); if (metadata == null) { return false; } // Check if session expired if (isSessionExpired(metadata)) { invalidateSession(sessionId); return false; } // Validate client IP (optional - can be too restrictive with proxies) if (!metadata.getClientIp().equals(clientIp)) { logger.warn("Session client IP mismatch: expected {}, got {}", metadata.getClientIp(), clientIp); // You might choose to invalidate the session here } // Update last access metadata.updateLastAccess(); return true; } public void invalidateSession(String sessionId) { SessionMetadata metadata = activeSessions.remove(sessionId); if (metadata != null) { invalidatedSessions.add(sessionId); logger.info("Session invalidated: {}, user: {}", sessionId, metadata.getUser().getUsername()); } } public void invalidateUserSessions(String username) { activeSessions.entrySet().removeIf(entry -> { if (username.equals(entry.getValue().getUser().getUsername())) { invalidatedSessions.add(entry.getKey()); logger.info("Session invalidated for user: {}, sessionId: {}", username, entry.getKey()); return true; } return false; }); } public SessionMetadata getSessionMetadata(String sessionId) { return activeSessions.get(sessionId); } public int getActiveSessionCount() { return activeSessions.size(); } public Map<String, Integer> getActiveSessionsByUser() { Map<String, Integer> countByUser = new HashMap<>(); for (SessionMetadata metadata : activeSessions.values()) { String username = metadata.getUser().getUsername(); countByUser.put(username, countByUser.getOrDefault(username, 0) + 1); } return countByUser; } private void enforceConcurrentSessionLimit(String username) { long userSessionCount = activeSessions.values().stream() .filter(metadata -> username.equals(metadata.getUser().getUsername())) .count(); if (userSessionCount >= maxSessionsPerUser) { // Invalidate oldest session for this user activeSessions.entrySet().stream() .filter(entry -> username.equals(entry.getValue().getUser().getUsername())) .min(Comparator.comparing(entry -> entry.getValue().getLastAccessTime())) .ifPresent(oldest -> invalidateSession(oldest.getKey())); logger.warn("Concurrent session limit exceeded for user: {}, invalidated oldest session", username); } } private boolean isSessionExpired(SessionMetadata metadata) { long currentTime = System.currentTimeMillis(); long inactiveTime = currentTime - metadata.getLastAccessTime(); return inactiveTime > sessionTimeoutMs; } private String generateSecureSessionId() { SecureRandom random = new SecureRandom(); byte[] bytes = new byte[32]; random.nextBytes(bytes); return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); } private void startSessionCleanupTask() { Timer timer = new Timer("SessionCleanup", true); timer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { cleanupExpiredSessions(); } }, 60 * 1000, 60 * 1000); // Run every minute } private void cleanupExpiredSessions() { long currentTime = System.currentTimeMillis(); int cleanedCount = 0; Iterator<Map.Entry<String, SessionMetadata>> iterator = activeSessions.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<String, SessionMetadata> entry = iterator.next(); SessionMetadata metadata = entry.getValue(); if (currentTime - metadata.getLastAccessTime() > sessionTimeoutMs) { iterator.remove(); invalidatedSessions.add(entry.getKey()); cleanedCount++; logger.debug("Cleaned up expired session: {}, user: {}", entry.getKey(), metadata.getUser().getUsername()); } } // Clean up invalidated sessions cache (keep only recent ones) if (invalidatedSessions.size() > 10000) { invalidatedSessions.clear(); } if (cleanedCount > 0) { logger.info("Session cleanup completed: {} expired sessions removed", cleanedCount); } } public static class SessionContext { private final String sessionId; private final User user; private final long creationTime; public SessionContext(String sessionId, User user, long creationTime) { this.sessionId = sessionId; this.user = user; this.creationTime = creationTime; } // Getters public String getSessionId() { return sessionId; } public User getUser() { return user; } public long getCreationTime() { return creationTime; } } public static class SessionMetadata { private final String sessionId; private final User user; private final String clientIp; private final String userAgent; private final long creationTime; private volatile long lastAccessTime; public SessionMetadata(String sessionId, User user, String clientIp, String userAgent) { this.sessionId = sessionId; this.user = user; this.clientIp = clientIp; this.userAgent = userAgent; this.creationTime = System.currentTimeMillis(); this.lastAccessTime = this.creationTime; } public void updateLastAccess() { this.lastAccessTime = System.currentTimeMillis(); } // Getters public String getSessionId() { return sessionId; } public User getUser() { return user; } public String getClientIp() { return clientIp; } public String getUserAgent() { return userAgent; } public long getCreationTime() { return creationTime; } public long getLastAccessTime() { return lastAccessTime; } } } Configuration Best Practices
1. Web.xml Configuration (Traditional Web Apps)
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee"> <!-- Session Configuration --> <session-config> <session-timeout>30</session-timeout> <cookie-config> <http-only>true</http-only> <secure>true</secure> <name>JSESSIONID</name> </cookie-config> <tracking-mode>COOKIE</tracking-mode> </session-config> <!-- Security Constraints --> <security-constraint> <web-resource-collection> <web-resource-name>Protected Resources</web-resource-name> <url-pattern>/secure/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>*</role-name> </auth-constraint> </security-constraint> <!-- Listeners --> <listener> <listener-class>com.example.SessionMonitoringListener</listener-class> </listener> <!-- Filters --> <filter> <filter-name>SecurityHeadersFilter</filter-name> <filter-class>com.example.SecurityHeadersFilter</filter-class> </filter> <filter-mapping> <filter-name>SecurityHeadersFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
Security Checklist
- [ ] Use secure, random session identifiers
- [ ] Implement proper session timeout
- [ ] Set HttpOnly and Secure flags on session cookies
- [ ] Implement CSRF protection
- [ ] Validate session integrity on each request
- [ ] Implement concurrent session control
- [ ] Secure session storage (encrypt if necessary)
- [ ] Implement proper logout functionality
- [ ] Set security headers (CSP, HSTS, etc.)
- [ ] Log session lifecycle events
- [ ] Monitor for suspicious session activity
This comprehensive session management implementation provides enterprise-grade security features while maintaining flexibility for different application architectures.