Session Management with Redis
Redis is the most popular choice for distributed session storage in horizontally-scaled web applications. Spring Session provides first-class integration.
The Problem with Sticky Sessionsโ
Without Redis sessions (local session):
User โ Load Balancer โ Instance A (session stored locally)
Next request โ Instance B (no session โ forced login!)
With Redis sessions:
User โ Load Balancer โ Instance A โ reads/writes from Redis
Next request โ Instance B โ reads/writes from same Redis โ seamless!
Spring Session with Redisโ
Dependencyโ
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Configurationโ
@Configuration
@EnableRedisHttpSession(
maxInactiveIntervalInSeconds = 3600 // 1 hour idle timeout
)
public class SessionConfig {
// Spring Session auto-configures with your RedisConnectionFactory bean
}
Or via application.yml:
spring:
session:
store-type: redis
redis:
namespace: "spring:session" # key prefix in Redis
flush-mode: on-save # or immediate
timeout: 3600s # session TTL
data:
redis:
host: localhost
port: 6379
That's it โ all HttpSession operations now use Redis automatically.
Custom Session Operationsโ
@RestController
public class AuthController {
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest req,
HttpSession session) {
User user = authService.authenticate(req.username(), req.password());
session.setAttribute("userId", user.getId());
session.setAttribute("roles", user.getRoles());
session.setAttribute("loginTime", Instant.now().toString());
return ResponseEntity.ok(Map.of("sessionId", session.getId()));
}
@GetMapping("/me")
public ResponseEntity<?> currentUser(HttpSession session) {
Long userId = (Long) session.getAttribute("userId");
if (userId == null) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
return ResponseEntity.ok(userService.findById(userId));
}
@PostMapping("/logout")
public ResponseEntity<?> logout(HttpSession session) {
session.invalidate(); // removes from Redis
return ResponseEntity.ok().build();
}
}
Programmatic Session Managementโ
Manage sessions directly via Spring Session's FindByIndexNameSessionRepository:
@Service
public class SessionManagementService {
@Autowired
private FindByIndexNameSessionRepository<? extends Session> sessionRepository;
// Find all sessions for a user (requires principal name indexing)
public Map<String, ? extends Session> getUserSessions(String username) {
return sessionRepository
.findByPrincipalName(username);
}
// Force logout a user from all devices
public void invalidateAllSessions(String username) {
sessionRepository.findByPrincipalName(username)
.keySet()
.forEach(sessionRepository::deleteById);
}
// Force logout from a specific session
public void invalidateSession(String sessionId) {
sessionRepository.deleteById(sessionId);
}
}
Enable Principal Name Indexingโ
@Configuration
@EnableRedisIndexedHttpSession
public class SessionConfig {
// Enables findByPrincipalName queries
}
Session Securityโ
Prevent Session Fixationโ
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.sessionManagement(session -> session
.sessionFixation().migrateSession() // new ID after login
.maximumSessions(3) // max 3 concurrent sessions
.sessionRegistry(sessionRegistry())
.expiredSessionStrategy(event ->
event.getResponse()
.sendError(HttpStatus.UNAUTHORIZED.value(),
"Session expired"))
);
return http.build();
}
@Bean
public SpringSessionBackedSessionRegistry<?> sessionRegistry() {
return new SpringSessionBackedSessionRegistry<>(sessionRepository);
}
}
Session Cookie Configurationโ
server:
servlet:
session:
cookie:
http-only: true
secure: true # HTTPS only
same-site: strict
max-age: 3600
name: SESSION
Viewing Sessions in Redisโ
# List all session keys
redis-cli KEYS "spring:session:*"
# Get session data
redis-cli HGETALL "spring:session:sessions:abc123def456"
# Check TTL
redis-cli TTL "spring:session:sessions:abc123def456"
# Delete a specific session
redis-cli DEL "spring:session:sessions:abc123def456"
Session Metricsโ
@Component
@Slf4j
public class SessionMetrics {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Scheduled(fixedDelay = 60_000)
public void reportActiveSessions() {
Set<String> sessionKeys = redisTemplate.keys("spring:session:sessions:*");
long activeCount = sessionKeys != null ? sessionKeys.size() : 0;
log.info("Active sessions: {}", activeCount);
// Report to Prometheus / CloudWatch / etc.
}
}
Comparison: Session Storage Optionsโ
| Storage | Scalability | Persistence | Speed | Setup |
|---|---|---|---|---|
| In-memory (local) | โ (sticky sessions) | โ | โ Fast | None |
| Redis | โ | โ Optional | โ Fast | Easy |
| Database (SQL) | โ | โ | โ ๏ธ Slower | Medium |
| Cookie (JWT) | โ | N/A (stateless) | โ Fast | Medium |
Best practice: Use Redis sessions for stateful workloads. Use JWT for stateless APIs. Combine both โ JWT for auth, Redis for rich session data.
Interview Questionsโ
Q: When should a team choose Redis sessions over stateless JWT-only auth?โ
A: When server-side session invalidation, concurrent session control, or rich mutable session state is required.
Q: What is the biggest operational risk in distributed session storage?โ
A: Redis unavailability can impact authentication flow, so HA design and fallback behavior are critical.
Q: How do you implement secure global logout across devices?โ
A: Index sessions by principal and invalidate all associated session IDs atomically.
Q: Why is session fixation protection mandatory after login?โ
A: It prevents attackers from reusing a pre-established session identifier.
Q: How do you balance session TTL and user experience?โ
A: Use idle timeout with optional sliding renewal, constrained by security and compliance policy.
Q: What monitoring should exist for session infrastructure?โ
A: Active session count, expiration rate, auth failure spikes, and Redis latency/error trends.