Secure Session Management in Java

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.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper