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.