Spring Boot Session Auto‑Configuration: How Filters and Repositories Work
This article walks through Spring Boot 2.3's session auto‑configuration process, detailing how core SessionConfiguration classes are registered, how the appropriate SessionRepository is selected based on store type, and how the SessionRepositoryFilter wraps requests and responses to manage session data with implementations such as Redis.
1 Auto‑Configuration
Spring Boot 2.3.12.RELEASE provides automatic configuration for HTTP session handling. The main entry point is SessionAutoConfiguration , which imports the core filter configuration and registers the appropriate SessionRepository implementation based on the selected store type.
<code>public class SessionAutoConfiguration {
// SessionRepositoryFilterConfiguration configures the core filter
// 3 core filters
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@Import({ ServletSessionRepositoryValidator.class, SessionRepositoryFilterConfiguration.class })
static class ServletSessionConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(SessionRepository.class)
@Import({ ServletSessionRepositoryImplementationValidator.class,
ServletSessionConfigurationImportSelector.class })
static class ServletSessionRepositoryConfiguration {
}
}
// This class registers all *SessionConfiguration classes such as RedisSessionConfiguration, JdbcSessionConfiguration, etc.
static class ServletSessionConfigurationImportSelector extends SessionConfigurationImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return super.selectImports(WebApplicationType.SERVLET);
}
}
}
</code>2 Core Session Configuration Objects
The framework registers a set of *SessionConfiguration classes (Redis, Mongo, JDBC, Hazelcast, NoOp) via SessionStoreMappings . These mappings are stored in an immutable map.
<code>final class SessionStoreMappings {
private static final Map<StoreType, Configurations> MAPPINGS;
static {
Map<StoreType, Configurations> mappings = new EnumMap<>(StoreType.class);
mappings.put(StoreType.REDIS, new Configurations(RedisSessionConfiguration.class, RedisReactiveSessionConfiguration.class));
mappings.put(StoreType.MONGODB, new Configurations(MongoSessionConfiguration.class, MongoReactiveSessionConfiguration.class));
mappings.put(StoreType.JDBC, new Configurations(JdbcSessionConfiguration.class, null));
mappings.put(StoreType.HAZELCAST, new Configurations(HazelcastSessionConfiguration.class, null));
mappings.put(StoreType.NONE, new Configurations(NoOpSessionConfiguration.class, NoOpReactiveSessionConfiguration.class));
MAPPINGS = Collections.unmodifiableMap(mappings);
}
}
</code>2.1 Register Session Configuration Classes
During import selection, SessionConfigurationImportSelector#selectImports iterates over all registered *SessionConfiguration classes and returns their fully qualified names.
<code>abstract static class SessionConfigurationImportSelector implements ImportSelector {
protected final String[] selectImports(WebApplicationType webApplicationType) {
// iterate over all StoreType values and map to configuration class names
return Arrays.stream(StoreType.values())
.map(type -> SessionStoreMappings.getConfigurationClass(webApplicationType, type))
.toArray(String[]::new);
}
}
</code>2.2 Register Session Store Object
Each concrete *SessionConfiguration defines a @Bean that creates a SessionRepository implementation. For Redis, SpringBootRedisHttpSessionConfiguration extends RedisHttpSessionConfiguration , which registers a RedisIndexedSessionRepository bean.
<code>@Configuration(proxyBeanMethods = false)
public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguration {
@Bean
public RedisIndexedSessionRepository sessionRepository() {
// ... create and configure repository ...
}
}
</code>3 Core Filter
3.1 Filter Registration
The SessionRepositoryFilterConfiguration registers SessionRepositoryFilter as a servlet filter.
<code>class SessionRepositoryFilterConfiguration {
@Bean
FilterRegistrationBean<SessionRepositoryFilter<?>> sessionRepositoryFilterRegistration(
SessionProperties sessionProperties, SessionRepositoryFilter<?> filter) {
FilterRegistrationBean<SessionRepositoryFilter<?>> registration = new FilterRegistrationBean<>(filter);
registration.setDispatcherTypes(getDispatcherTypes(sessionProperties));
registration.setOrder(sessionProperties.getServlet().getFilterOrder());
return registration;
}
}
</code>3.2 Core Filter Methods
The filter wraps the incoming HttpServletRequest and HttpServletResponse with custom wrappers, delegates to the filter chain, and finally commits the session.
<code>@Order(SessionRepositoryFilter.DEFAULT_ORDER)
public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(request, response);
SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(wrappedRequest, response);
try {
filterChain.doFilter(wrappedRequest, wrappedResponse);
} finally {
wrappedRequest.commitSession();
}
}
}
</code>The SessionRepositoryRequestWrapper provides commitSession() , which saves the session via the configured SessionRepository (e.g., Redis or JDBC).
<code>private final class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper {
private void commitSession() {
HttpSessionWrapper wrappedSession = getCurrentSession();
if (wrappedSession == null) {
if (isInvalidateClientSession()) {
SessionRepositoryFilter.this.httpSessionIdResolver.expireSession(this, this.response);
}
} else {
S session = wrappedSession.getSession();
clearRequestedSessionCache();
SessionRepositoryFilter.this.sessionRepository.save(session);
}
}
}
</code>The request wrapper also overrides getSession() to create a new HttpSessionWrapper when needed, delegating attribute handling to the underlying Session implementation.
<code>public class HttpSessionAdapter<S extends Session> implements HttpSession {
private S session;
public Object getAttribute(String name) {
return this.session.getAttribute(name);
}
public void setAttribute(String name, Object value) {
Object oldValue = this.session.getAttribute(name);
this.session.setAttribute(name, value);
if (value != oldValue) {
if (oldValue instanceof HttpSessionBindingListener) {
((HttpSessionBindingListener) oldValue).valueUnbound(new HttpSessionBindingEvent(this, name, oldValue));
}
if (value instanceof HttpSessionBindingListener) {
((HttpSessionBindingListener) value).valueBound(new HttpSessionBindingEvent(this, name, value));
}
}
}
}
</code>3.3 Session Data Storage
When the session is saved, RedisIndexedSessionRepository persists the delta map to Redis and updates index keys for principal names.
<code>public class RedisIndexedSessionRepository implements FindByIndexNameSessionRepository<RedisIndexedSessionRepository.RedisSession>, MessageListener {
public void save(RedisSession session) {
session.save();
if (session.isNew) {
String sessionCreatedKey = getSessionCreatedChannel(session.getId());
this.sessionRedisOperations.convertAndSend(sessionCreatedKey, session.delta);
session.isNew = false;
}
}
static final class RedisSession implements Session {
private Map<String, Object> delta = new HashMap<>();
private void save() {
saveChangeSessionId();
saveDelta();
}
private void saveDelta() {
if (this.delta.isEmpty()) return;
String sessionId = getId();
getSessionBoundHashOperations(sessionId).putAll(this.delta);
// handle principal index updates ...
this.delta = new HashMap<>(this.delta.size());
Long originalExpiration = (this.originalLastAccessTime != null)
? this.originalLastAccessTime.plus(getMaxInactiveInterval()).toEpochMilli() : null;
this.expirationPolicy.onExpirationUpdated(originalExpiration, this);
}
}
}
</code>In summary, the filter chain processes the request with custom wrappers, the session data is stored in the chosen backend (Redis in this example), and the filter ensures that changes are committed after the request completes.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.