I am trying to implement a 'force logout' functionality in my Spring Boot app (for example, after an admin disables a user account).
I followed the steps as specified in various tutorials to get access to the session registry, in order to expire the session of a user (baeldung step 6 and verbose version on myyurt; also related SO).
However, after registering the SessionRegistryImpl as a @Bean, when using the debugger, I see there are 2 different instances in the dependency injection mechanism:
one sessionRegistry instance is used by Spring Security at login and logout and holds the principals and sessions as expected. The screenshot below was taken after a login - I had a breakpoint in the registerNewSession() method. Notice the id and the maps with already logged in users.
the other sessionRegistry instance is only given to my own SessionManager class which requires SessionRegistry as a dependency and calls getAllPrincipals(). Notice the id is different and the maps are empty (I called getAllPrincipals() after I logged in multiple times and took the first screenshot)
Class where I register the sessionRegistry bean (I removed the unnecessary code and left only my custom filters, in case it might have something to do with Springs auto-configurations):
@EnableWebSecurity
class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public static HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
@Bean
public static SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(myFirstFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(mySecondFilter, FilterSecurityInterceptor.class)
.formLogin() // skipping details
.and()
.x509() // skipping details
.and()
.logout()
.invalidateHttpSession(true)
.permitAll()
.and()
.authorizeRequests() // skipping details
.and()
.sessionManagement()
.invalidSessionUrl("/login")
.enableSessionUrlRewriting(false)
.maximumSessions(-1)
.maxSessionsPreventsLogin(false)
.sessionRegistry(sessionRegistry())
.expiredUrl("/login?expire");
}
}
Class where I consumer the sessionRegistry dependency:
@Component
class DefaultSessionManager implements SessionManager {
private final SessionRegistry sessionRegistry;
@Autowired public DefaultSessionManager(SessionRegistry sessionRegistry) {
this.sessionRegistry = sessionRegistry;
}
public void expireUserSessions(String username) {
for (Object principal : sessionRegistry.getAllPrincipals()) {
// do stuff, but won't enter because the list is empty
}
}
}
If it helps, I looked up the beans setup with the Actuator /beans endpoint, and this is what it returns
{
"bean": "defaultSessionManager",
"aliases":
[
],
"scope": "singleton",
"type": "com.foo.bar.DefaultSessionManager",
"resource": // file path
"dependencies":
[
"sessionRegistry"
]
},
{
"bean": "httpSessionEventPublisher",
"aliases":
[
],
"scope": "singleton",
"type": "org.springframework.security.web.session.HttpSessionEventPublisher",
"resource": "class path resource [com/foo/bar/SecurityConfig.class]",
"dependencies":
[
]
},
{
"bean": "sessionRegistry",
"aliases":
[
],
"scope": "singleton",
"type": "org.springframework.security.core.session.SessionRegistryImpl",
"resource": "class path resource [com/foo/bar/SecurityConfig.class]",
"dependencies":
[
]
},
How can there be two different instances in the DI system if all are declared as Singleton? Do you have any tips on what might be wrong?
I'm using spring-boot-starter-parent 1.5.2.RELEASE, which uses Spring Security 4.2.2.RELEASE.