Hi Anthony,
Thanks for the very descriptive bug report. I’ll have a look at fixing this shortly.
Scott Rossillo
Smartling | Senior Software Engineer
srossillo(a)smartling.com
On Mar 21, 2016, at 7:26 PM, Anthony Fryer
<Anthony.Fryer(a)virginaustralia.com> wrote:
I’ve noticed some issues when testing single logout with the spring security adapter.
I setup the admin url for the test application that used the spring security adapter in
keycloak and tested logging out from keycloak and it didn’t invalidate the session. This
is consistent with what I saw in other environments while testing. I did some digging and
found that the spring adapter isn’t working correctly for single log out in my
environments. We’re not using spring boot so not sure if that might be a reason why its
not working out of the box.
The issue is with the org.keycloak.adapters.springsecurity.management.HtttpSessionManager
class. This implements javax.servlet.http.HttpSessionListener to receive events when
sessions are created and stores the sessions in a hash map. When you do a logout from
keycloak, it sends a POST request to <admin_url>/k_logout. This results in a call
to the HttpSessionManager.logoutHttpSessions method with the session id passed in as an
argument. This method attempts to lookup the session in the hashmap and call the
invalidate() method.
The problem is by default the HttpSessionManager class isn’t receiving the session create
events. You need to configure it as a listener in web.xml to enable that. But even if
you do that it still doesn’t work because the servlet container will create a instance of
the class, but spring will also create another instance when creating the keycloak beans
and this new instance is the one passed into the KeycloakPreAuthActionsFilter constructor.
So the instance that is created by the servlet container is the one receiving the session
create event and the one used by spring isn’t receiving any events but is the one used to
do the logoutHttpSessions() call. The spring instance has no sessions in the hashmap, so
logoutHttpSessions() does nothing.
The fix is to make a new version of HttpSessionManager that implements
org.keycloak.adapters.spi.UserSessionManagement
andorg.springframework.context.ApplicationListener<ApplicationEvent>, which is a
spring interface that receives session create/destroy events. In web.xml you need to
register org.springframework.security.web.session.HttpSessionEventPublisher as a listener
so spring will receive those events from the servlet container. Then in the spring
config, you need the KeycloakPreAuthActionsFilter to be initialized with the new
HttpSessionManager instead of the default one.
The HttpSessionManager class that works for me is below…
package my.keycloak;
import java.util.List;
import javax.servlet.http.HttpSession;
import org.keycloak.adapters.spi.UserSessionManagement;
import org.keycloak.adapters.springsecurity.management.LocalSessionManagementStrategy;
import org.keycloak.adapters.springsecurity.management.SessionManagementStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.security.web.session.HttpSessionCreatedEvent;
import org.springframework.security.web.session.HttpSessionDestroyedEvent;
public class HttpSessionManager implements UserSessionManagement,
ApplicationListener<ApplicationEvent> {
private static final Logger log =
LoggerFactory.getLogger(HttpSessionManager.class);
private SessionManagementStrategy sessions = new LocalSessionManagementStrategy();
@Override
public void logoutAll() {
log <
http://log.info/>.info <
http://log.info/>("Received
request to log out all users.");
for (HttpSession session : sessions.getAll()) {
session.invalidate();
}
sessions.clear();
}
@Override
public void logoutHttpSessions(List<String> ids) {
log <
http://log.info/>.info <
http://log.info/>("Received
request to log out {} session(s): {}", ids.size(), ids);
for (String id : ids) {
HttpSession session = sessions.remove(id);
if (session != null) {
session.invalidate();
}
}
sessions.clear();
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof HttpSessionCreatedEvent) {
HttpSessionCreatedEvent e = (HttpSessionCreatedEvent)event;
HttpSession session = e.getSession();
log.debug("Session created: {}", session.getId());
sessions.store(session);
} else if (event instanceof HttpSessionDestroyedEvent) {
HttpSessionDestroyedEvent e = (HttpSessionDestroyedEvent)event;
HttpSession session = e.getSession();
sessions.remove(session.getId());
log.debug("Session destroyed: {}", session.getId());
}
}
}
The keycloak config changes are below…
@Configuration
@EnableWebSecurity
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
public class WebSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(keycloakAuthenticationProvider());
}
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
@Bean
protected KeycloakPreAuthActionsFilter keycloakPreAuthActionsFilter() {
return new KeycloakPreAuthActionsFilter(springHttpSessionManager());
}
@Bean
protected my.keycloak.HttpSessionManager springHttpSessionManager() {
return new my.keycloak.HttpSessionManager();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/sso/logout"))
.and()
.authorizeRequests()
.antMatchers("/user*").authenticated()
.anyRequest().permitAll();
}
}
and web.xml needs this added to it…
<listener>
<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>
After making the above changes, log out from the keycloak admin console works as
expected.
Regards,
Anthony Fryer
The content of this e-mail, including any attachments, is a confidential communication
between Virgin Australia Airlines Pty Ltd (Virgin Australia) or its related entities (or
the sender if this email is a private communication) and the intended addressee and is for
the sole use of that intended addressee. If you are not the intended addressee, any use,
interference with, disclosure or copying of this material is unauthorized and prohibited.
If you have received this e-mail in error please contact the sender immediately and then
delete the message and any attachment(s). There is no warranty that this email is error,
virus or defect free. This email is also subject to copyright. No part of it should be
reproduced, adapted or communicated without the written consent of the copyright owner. If
this is a private communication it does not represent the views of Virgin Australia or its
related entities. Please be aware that the contents of any emails sent to or from Virgin
Australia or its related entities may be periodically monitored and reviewed. Virgin
Australia and its related entities respect your privacy. Our privacy policy can be
accessed from our website:
www.virginaustralia.com
<
http://www.virginaustralia.com/>______________________________________...
keycloak-user mailing list
keycloak-user(a)lists.jboss.org <mailto:keycloak-user@lists.jboss.org>
https://lists.jboss.org/mailman/listinfo/keycloak-user
<
https://lists.jboss.org/mailman/listinfo/keycloak-user>