Spring - Expiring all Sessions of a User
Recently, I faced a problem where I had to expire all sessions of a user in Spring MVC. There are answered stackoverflow questions but either they were answered in xml config or they did not cover all aspects. Here is how I managed to do it:
Following class is the configuration class and I’ll explain it step by step:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.maximumSessions(100) //(1)
.maxSessionsPreventsLogin(false) //(2)
.expiredUrl("/auth/login") //(3)
.sessionRegistry(sessionRegistry()) //(4)
;
}
@Bean
SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
@Bean
public static ServletListenerRegistrationBean httpSessionEventPublisher() { //(5)
return new ServletListenerRegistrationBean(new HttpSessionEventPublisher());
}
}
(1) ConcurrencyControlConfigurer: To register SessionRegistry
bean, we
need ConcurrencyControlConfigurer
which is only provided by
http.sessionManagement().maximumSessions(int)
call. That’s why I specified it
to be a large number, because I don’t want to limit users.
(2) Prevent login: I had to limit maximum session count but I don’t want to prevent a user from logging in. Here is the javadoc:
If true, prevents a user from authenticating when the SessionManagementConfigurer.maximumSessions(int) has been reached. Otherwise (default), the user who authenticates is allowed access and an existing user’s session is expired. The user’s who’s session is forcibly expired is sent to expiredUrl(String). The advantage of this approach is if a user accidentally does not log out, there is no need for an administrator to intervene or wait till their session expires.
(3) Expired url: When user sessions are expired successfully, user was facing this simple text when re-logging in:
This session has been expired (possibly due to multiple concurrent logins being attempted as the same user).
Instead, I specified the redirection url by this config. Users go to login page when I kill their session in server.
(4) Session Registry: SessionRegistry bean is necessary to reach sessions stored on server.
(5) HttpSessionEventPublisher: To tell Spring to store sessions in the registry, we need HttpSessionEventPublisher bean.
The configuration is completed. Now Spring stores every session in
SessionRegistry
. We can traverse the registered sessions to retrive session
information of all authenticated users at that moment. Here is a utility class
to find and expire user sessions:
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
@Component
public class SessionUtils {
@Autowired
private SessionRegistry sessionRegistry;
public void expireUserSessions(String username) {
for (Object principal : sessionRegistry.getAllPrincipals()) {
if (principal instanceof User) {
UserDetails userDetails = (UserDetails) principal;
if (userDetails.getUsername().equals(username)) {
for (SessionInformation information : sessionRegistry.getAllSessions(userDetails, true)) {
information.expireNow();
}
}
}
}
}
}
After calling this method, all of user sessions are expired and when user makes
a new request, s/he will be processed through logout steps. Because we
registered concurrent session configuration above, ConcurrentSessionFilter
is
inserted by Spring and following code segment runs for every request:
if (session != null) {
SessionInformation info = sessionRegistry.getSessionInformation(session.getId());
if (info != null) {
if (info.isExpired()) {
// Expired - abort processing
doLogout(request, response); //!!!!
String targetUrl = determineExpiredUrl(request, info);
if (targetUrl != null) {
redirectStrategy.sendRedirect(request, response, targetUrl);
return;
} else {
response.getWriter().print("This session has been expired (possibly due to multiple concurrent " +
"logins being attempted as the same user).");
response.flushBuffer();
}
return;
} else {
// Non-expired - update last request date/time
sessionRegistry.refreshLastRequest(info.getSessionId());
}
}
}
See the line with exclamation points? That’s where Spring decides that session is expired and logs out user.
I tried this in a handler where I wanted to expire all user sessions and redirect requesting session to some other page with flash attributes. All sessions are expired as expected, but the request is not redirected with flash attributes as I intended because of logout logic, remember that.