I’ve been working on a Spring Boot project recently and I needed to set the session expiry time.

I was using spring-boot-starter-web, and I was getting inconsistent behaviour from Tomcat webserver (I suspect that Tomcat was setting it’s own timeout value, which was overriding the 10 second in application.properties).

I decided to use Undertow.

I did this by putting an exclusion in the pom.xml, which excludes the Tomcat dependency, and then adding the Undertow dependency.

I also added an explicit SessionTimeoutListener with an overridden sessionDestroyed method. This is referenced from ListenerConfig.java. This has an explicit Session has timed out debug message.

application.properties - 10 second timeout:

server.servlet.session.timeout=10
logging.level.root=INFO

pom.xml - with tomcat exclusion and undertow dependency:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.example</groupId>
    <artifactId>Session</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>Archetype - Session</name>
    <url>http://maven.apache.org</url>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.2</version>
        <relativePath/>
    </parent>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <!-- Exclude the Tomcat dependency -->
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-undertow</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

SessionTimeoutListener.java - with sessionDestroyed method:

import jakarta.servlet.http.HttpSessionEvent;
import jakarta.servlet.http.HttpSessionListener;

public class SessionTimeoutListener implements HttpSessionListener {

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        System.out.println("Session has timed out: " + se.getSession().getId());
    }
}

ListenerConfig.java - with sessionTimeoutListener method:

import com.chocksaway.session.listener.SessionTimeoutListener;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ListenerConfig {

    @Bean
    public ServletListenerRegistrationBean<SessionTimeoutListener> sessionTimeoutListener() {
        return new ServletListenerRegistrationBean<>(new SessionTimeoutListener());
    }
}

SecurityConfig.java - with sessionManagement:

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(authorizeRequests -> authorizeRequests
                .requestMatchers("/auth/login").permitAll()
                .requestMatchers("/auth/logout").permitAll()
                .anyRequest().authenticated()
            )
            .formLogin(formLogin -> formLogin
                .loginPage("/auth/login")
                .defaultSuccessUrl("/home", true)
                .successHandler(authenticationSuccessHandler())
            )
            .logout(logout -> logout.logoutUrl("/auth/logout")
                .logoutSuccessUrl("/auth/login")
                .invalidateHttpSession(true)
                .deleteCookies("JSESSIONID")
            )
            .sessionManagement(sessionManagement -> sessionManagement
                .invalidSessionUrl("/auth/login")
                .maximumSessions(1)
                .expiredUrl("/auth/login")
            );

        return http.build();
    }

    @Bean
    public AuthenticationSuccessHandler authenticationSuccessHandler() {
        return (HttpServletRequest request, HttpServletResponse response,
                org.springframework.security.core.Authentication authentication) -> {
            request.getSession().setAttribute("username", authentication.getName());
            response.sendRedirect("/home");
        };
    }
}