Spring Session - Quick Reference
Deep Knowledge: Use
mcp__documentation__fetch_docswith technology:spring-sessionfor comprehensive documentation.
Dependencies
<!-- Redis Backend -->
<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>
<!-- JDBC Backend -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-jdbc</artifactId>
</dependency>
<!-- Hazelcast Backend -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-hazelcast</artifactId>
</dependency>
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Load Balancer │
└─────────────────────────────────────────────────────────────┘
│
┌───────────────────┼───────────────────┐
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Instance 1 │ │ Instance 2 │ │ Instance 3 │
│ App Server │ │ App Server │ │ App Server │
└───────┬───────┘ └───────┬───────┘ └───────┬───────┘
│ │ │
└───────────────────┼───────────────────┘
▼
┌─────────────────────────┐
│ Session Store │
│ (Redis/JDBC/Hazelcast)│
└─────────────────────────┘
Redis Configuration
application.yml
spring:
data:
redis:
host: localhost
port: 6379
password: ${REDIS_PASSWORD:}
session:
store-type: redis
timeout: 30m
redis:
namespace: spring:session
flush-mode: on_save # immediate or on_save
cleanup-cron: "0 * * * * *" # Every minute
Java Configuration
@Configuration
@EnableRedisHttpSession(
maxInactiveIntervalInSeconds = 1800, // 30 minutes
redisNamespace = "myapp:session",
flushMode = FlushMode.ON_SAVE
)
public class SessionConfig {
@Bean
public LettuceConnectionFactory connectionFactory() {
return new LettuceConnectionFactory();
}
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
return new GenericJackson2JsonRedisSerializer();
}
}
JDBC Configuration
application.yml
spring:
session:
store-type: jdbc
timeout: 30m
jdbc:
initialize-schema: always # always, embedded, never
table-name: SPRING_SESSION
cleanup-cron: "0 * * * * *"
Schema
-- Auto-created with initialize-schema: always
-- Or create manually:
CREATE TABLE SPRING_SESSION (
PRIMARY_ID CHAR(36) NOT NULL,
SESSION_ID CHAR(36) NOT NULL,
CREATION_TIME BIGINT NOT NULL,
LAST_ACCESS_TIME BIGINT NOT NULL,
MAX_INACTIVE_INTERVAL INT NOT NULL,
EXPIRY_TIME BIGINT NOT NULL,
PRINCIPAL_NAME VARCHAR(100),
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
);
CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
SESSION_PRIMARY_ID CHAR(36) NOT NULL,
ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
ATTRIBUTE_BYTES BYTEA NOT NULL,
CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME),
CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID)
REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE
);
Session Usage
Controller
@RestController
@RequestMapping("/api")
public class SessionController {
@GetMapping("/session/info")
public Map<String, Object> getSessionInfo(HttpSession session) {
return Map.of(
"sessionId", session.getId(),
"creationTime", new Date(session.getCreationTime()),
"lastAccessedTime", new Date(session.getLastAccessedTime()),
"maxInactiveInterval", session.getMaxInactiveInterval()
);
}
@PostMapping("/session/attribute")
public void setAttribute(
HttpSession session,
@RequestParam String key,
@RequestParam String value) {
session.setAttribute(key, value);
}
@GetMapping("/session/attribute/{key}")
public String getAttribute(HttpSession session, @PathVariable String key) {
return (String) session.getAttribute(key);
}
@DeleteMapping("/session/invalidate")
public void invalidateSession(HttpSession session) {
session.invalidate();
}
}
@SessionAttributes
@Controller
@SessionAttributes("cart")
public class CartController {
@ModelAttribute("cart")
public Cart createCart() {
return new Cart();
}
@PostMapping("/cart/add")
public String addToCart(
@ModelAttribute("cart") Cart cart,
@RequestParam Long productId) {
cart.addItem(productId);
return "redirect:/cart";
}
@PostMapping("/cart/checkout")
public String checkout(
@ModelAttribute("cart") Cart cart,
SessionStatus status) {
orderService.createOrder(cart);
status.setComplete(); // Remove from session
return "redirect:/orders";
}
}
@SessionScope Bean
@Component
@SessionScope
public class UserPreferences implements Serializable {
private String theme = "light";
private String language = "en";
private int pageSize = 20;
// getters and setters
}
@RestController
public class PreferencesController {
@Autowired
private UserPreferences preferences; // Session-scoped
@PutMapping("/preferences/theme")
public void setTheme(@RequestParam String theme) {
preferences.setTheme(theme);
}
}
Security Integration
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.maximumSessions(1) // One session per user
.maxSessionsPreventsLogin(true) // Prevent new login
.sessionRegistry(sessionRegistry())
)
.logout(logout -> logout
.invalidateHttpSession(true)
.deleteCookies("SESSION")
);
return http.build();
}
@Bean
public SpringSessionBackedSessionRegistry<?> sessionRegistry() {
return new SpringSessionBackedSessionRegistry<>(sessionRepository);
}
}
Find Sessions by Principal
@Service
@RequiredArgsConstructor
public class SessionManagementService {
private final FindByIndexNameSessionRepository<?> sessionRepository;
public Map<String, ?> findSessionsByUsername(String username) {
return sessionRepository.findByPrincipalName(username);
}
public void invalidateUserSessions(String username) {
Map<String, ?> sessions = sessionRepository.findByPrincipalName(username);
sessions.keySet().forEach(sessionRepository::deleteById);
}
}
Custom Session Repository
@Configuration
@EnableRedisHttpSession
public class CustomSessionConfig {
@Bean
public SessionRepositoryCustomizer<RedisIndexedSessionRepository> customizer() {
return sessionRepository -> {
sessionRepository.setDefaultMaxInactiveInterval(Duration.ofMinutes(30));
sessionRepository.setRedisKeyNamespace("app:sessions");
};
}
}
Session Events
@Component
@Slf4j
public class SessionEventListener {
@EventListener
public void onSessionCreated(SessionCreatedEvent event) {
log.info("Session created: {}", event.getSessionId());
}
@EventListener
public void onSessionDeleted(SessionDeletedEvent event) {
log.info("Session deleted: {}", event.getSessionId());
}
@EventListener
public void onSessionExpired(SessionExpiredEvent event) {
log.info("Session expired: {}", event.getSessionId());
}
}
Cookie Configuration
@Configuration
public class CookieConfig {
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookieName("SESSIONID");
serializer.setCookiePath("/");
serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$");
serializer.setSameSite("Strict");
serializer.setUseSecureCookie(true);
serializer.setUseHttpOnlyCookie(true);
serializer.setCookieMaxAge(3600); // 1 hour
return serializer;
}
}
Header-Based Session (REST APIs)
@Configuration
@EnableRedisHttpSession
public class HeaderSessionConfig {
@Bean
public HttpSessionIdResolver httpSessionIdResolver() {
return HeaderHttpSessionIdResolver.xAuthToken();
// Or custom header:
// return new HeaderHttpSessionIdResolver("X-Session-Id");
}
}
// Client sends: X-Auth-Token: <session-id>
WebSocket Session
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(new SessionChannelInterceptor());
}
}
public class SessionChannelInterceptor implements ChannelInterceptor {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
// Access session attributes
Map<String, Object> sessionAttributes = accessor.getSessionAttributes();
}
return message;
}
}
Testing
@SpringBootTest
@AutoConfigureMockMvc
class SessionControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
void shouldMaintainSessionAcrossRequests() throws Exception {
// First request - set attribute
MvcResult result = mockMvc.perform(post("/api/session/attribute")
.param("key", "user")
.param("value", "john"))
.andExpect(status().isOk())
.andReturn();
MockHttpSession session = (MockHttpSession) result.getRequest().getSession();
// Second request - get attribute with same session
mockMvc.perform(get("/api/session/attribute/user")
.session(session))
.andExpect(status().isOk())
.andExpect(content().string("john"));
}
}
Best Practices
| Do | Don't | |----|-------| | Use Redis for distributed sessions | Use in-memory sessions in cluster | | Configure proper timeout | Infinite session timeout | | Serialize session data properly | Store non-serializable objects | | Clean up expired sessions | Let sessions accumulate | | Secure session cookies | Use insecure cookies |
Production Checklist
- [ ] Distributed store configured (Redis/JDBC)
- [ ] Session timeout set appropriately
- [ ] Cookie security (Secure, HttpOnly, SameSite)
- [ ] Cleanup cron configured
- [ ] Maximum sessions per user
- [ ] Session fixation protection
- [ ] Serialization configured (JSON)
- [ ] Monitoring session count
- [ ] Redis cluster for HA
- [ ] Session events logging
When NOT to Use This Skill
- Stateless JWT auth - Use
jwtorspring-securityskills - OAuth2 tokens - Use
oauth2skill - Simple single-instance apps - In-memory sessions suffice
- REST APIs - Consider stateless design
Anti-Patterns
| Anti-Pattern | Problem | Solution | |--------------|---------|----------| | In-memory sessions in cluster | Session lost on failover | Use Redis/JDBC store | | Infinite session timeout | Resource exhaustion | Set appropriate timeout | | Non-serializable objects | Session save fails | Ensure Serializable | | No cleanup cron | Sessions accumulate | Configure cleanup | | Insecure cookies | Session hijacking | Use Secure, HttpOnly |
Quick Troubleshooting
| Problem | Diagnostic | Fix | |---------|------------|-----| | Session not persisted | Check store-type | Configure Redis/JDBC | | Session lost between nodes | Check store connectivity | Verify Redis/DB connection | | Serialization error | Check object types | Make objects Serializable | | Cookie not sent | Check cookie config | Enable Secure for HTTPS | | Session expires too fast | Check timeout | Increase session timeout |
微信扫一扫