Get security context from SessionDestroyedEvent in Spring Session - java

I am using Spring Session 1.0.1. I need to execute some logic when the user logs out, and I need to rely on the HTTP session being invalidated to cover the case where the user fails to explicitly log out.
The standard Spring Security SessionDestroyedEvent includes any applicable SecurityContext, but the Spring Session version of SessionDestroyedEvent only contains the session id. By the time this event fires, the session is no longer held by the SessionRepository so it can't be looked up by id.
Is there any way to retrieve the SecurityContext from the expired session using Spring Session?

Unfortunately there is not. The problem is that at the time Redis fires the event, the session is already gone. Furthermore, the event received from Redis does not contain the original information. This means there is no way to retrieve the SecurityContext.
For updates on this please track spring-projects/spring-session/issues/4

For sring-session 1.1+ with Redis
https://docs.spring.io/spring-session/docs/current/reference/html5/#httpsession-httpsessionlistener
You must configure HttpSessionEventPublisher and after that spring-session will propagate sessionDestroy event
#Configuration
#EnableRedisHttpSession
public class RedisHttpSessionConfig {
#Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
// ...
}
So you can use standard spting SessionDestroyedEvent listener
#Component
public class SessionDestroyListener implements ApplicationListener<SessionDestroyedEvent> {
#Override
public void onApplicationEvent(SessionDestroyedEvent event) {
logger.debug("session destroyed {}", event.getId());
if(!event.getSecurityContexts().isEmpty()) {
...
}
}
}

Related

How can you set org.apache.tomcat.websocket.BLOCKING_SEND_TIMEOUT?

What's the expected way to set org.apache.tomcat.websocket.BLOCKING_SEND_TIMEOUT to increase a tomcat websocket timeout? Tomcat documentation states the following:
This may be changed by setting the property
org.apache.tomcat.websocket.BLOCKING_SEND_TIMEOUT in the user
properties collection attached to the WebSocket session.
The WebSocketSession I see available in the TextWebSocketHandler's afterConnectionEstablished method doesn't apear to have user properties. So, I assume that's not what the documentation means. In looking at TomcatRequestUpgradeStrategy, it appears to me that it never looks at an endpoint user properties. It also doesn't appear to me that you can overwrite TomcatRequestUpgradeStrategy since AbstractHandshakeHandler has a hardcoded class name for TomcatRequestUpgradeStrategy.
Please help.
org.apache.tomcat.websocket.BLOCKING_SEND_TIMEOUT is an user property you need to set on WebSocket API's Session, not the Spring abstractions of this interface.
You can configure it in the afterConnectionEstablished method by casting the Spring WebSocketSession to NativeWebSocketSession and retrieving the underlying WebSocket API session:
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
if (session instanceof NativeWebSocketSession) {
final Session nativeSession = ((NativeWebSocketSession) session).getNativeSession(Session.class);
if (nativeSession != null) {
nativeSession.getUserProperties()
.put("org.apache.tomcat.websocket.BLOCKING_SEND_TIMEOUT", 60_000L);
}
}
}

Spring boot session timeout event listener

I want to perform a custom event when a user is logged out from a session timeout. The user is successfully logged out after exactly the length of time specified by my application.properties:
server.servlet.session.timeout=10
server.servlet.session.cookie.max-age=10
I have found a few similar solutions which involve a SessionDestroyedEvent, for example:
#Slf4j
#Component
public class SessionExpiredListener implements ApplicationListener<SessionDestroyedEvent> {
#Override
public void onApplicationEvent(SessionDestroyedEvent event) {
for (SecurityContext securityContext : event.getSecurityContexts()) {
Authentication authentication = securityContext.getAuthentication();
UserPrincipal user = (UserPrincipal) authentication.getPrincipal(); // UserPrincipal is my custom Principal class
log.debug("Session expired!" + user.getUsername());
// do custom event handling
}
}
}
The problem is the SessionDestroyedEvent is not triggered at the same time as the session timeout, in my tests it has triggered up to 5 minutes after the session has expired.
I have also tried using sessionDestroyed in HttpSessionListener but with similar results.
Is there an event that will trigger exactly when the session expires, or is there some way to achieve this?
The sessionDestroyed() method is called when the web container expires the session.
In Tomcat, session expirations happens every minute, and I think it is the case with other servlet containers.
So, even after the session times out there could be a delay until the next detection of expirations.
Session management is done by servlet container, and your application is getting notification from it.
And there is no way to be notified at the exact time of session expiration.
I also had handle the event when the user is logged out by session timeout. For me, this solution was helpfull: https://stackoverflow.com/a/18128496/4074871
Additionally I had to register the HttpSessionEventPublisher as mentioned in https://stackoverflow.com/a/24957247/4074871 because I had no web.xml for listener registration.

Websession invalidation not working from Spring Boot 2.0.2

I wanted to upgrade my Spring Webflux project from Spring Boot 2.0.1 to Spring Boot 2.0.3. In my project, my sessions are backed by Spring Session Data Redis. When upgrading the Spring Boot version, I noticed a websession invalidation problem from Spring Boot 2.0.2+.
In Spring Boot 2.0.1, I invalidated my sessions this way :
webSession.invalidate().subscribe();
This caused my current session to be destroyed, and a new one was generated with a new session ID, creation time, etc.
However, from Spring Boot 2.0.2, the same code doesn't seem to fully destroy the session. The current session information are just "cleared", and the same session ID is still used afterward. This is a problem because it causes a null pointer exception in ReactiveRedisOperationsSessionRepository.class :
private static final class SessionMapper implements Function<Map<String, Object>, MapSession> {
private final String id;
private SessionMapper(String id) {
this.id = id;
}
public MapSession apply(Map<String, Object> map) {
MapSession session = new MapSession(this.id);
session.setCreationTime(Instant.ofEpochMilli((Long)map.get("creationTime")));
session.setLastAccessedTime(Instant.ofEpochMilli((Long)map.get("lastAccessedTime")));
session.setMaxInactiveInterval(Duration.ofSeconds((long)(Integer)map.get("maxInactiveInterval")));
map.forEach((name, value) -> {
if (name.startsWith("sessionAttr:")) {
session.setAttribute(name.substring("sessionAttr:".length()), value);
}
});
return session;
}
}
As session data are empty (no creation time, etc.), the following line raises a NPE:
session.setCreationTime(Instant.ofEpochMilli((Long)map.get("creationTime")));
Is it a bug or have I missed something new in Spring Boot 2.0.2+ regarding Spring Session?
UPDATE
To provide more information, I created a sample project reproducing the problem: https://github.com/adsanche/test-redis-session
This project contains a simple controller exposing two endpoints:
#Controller
public class HelloController {
#GetMapping(value = "/hello")
public String hello(final WebSession webSession) {
webSession.getAttributes().put("test", "TEST");
return "index";
}
#GetMapping(value = "/invalidate")
public String invalidate(final WebSession webSession) {
webSession.invalidate().subscribe();
return UrlBasedViewResolver.REDIRECT_URL_PREFIX + "/hello";
}
}
Behavior when running the project with Spring Boot 2.0.1
Go to the first endpoint: http://localhost:8080/hello
State of the session in Redis:
We notice the session ID starting with 7546ff, and the session data containing the "test" attribute plus the default session information (creation/last access time, etc.).
Go to the second endpoint: http://localhost:8080/invalidate
The current session is invalidated, and a redirection is performed on "/hello", where the test attribute is added in a new web session.
State of the new session in Redis:
We notice the new session ID starting with ba7de, and the new session data still containing the test attribute plus the default session information.
Now, let's reproduce this scenario on the same project with Spring Boot 2.0.3.
Behavior when running the project with Spring Boot 2.0.3
Go to the first endpoint: http://localhost:8080/hello
State of the session in Redis:
We notice the session ID starting with 12d61, and the session data containing the "test" attribute plus the default session information (creation/last access time, etc.).
Go to the second endpoint: http://localhost:8080/invalidate
State of the new session in Redis:
We notice here that the same session ID is still used, but the session has been cleared even from its default information (creation date, etc.). So, when it is invoked again, a NPE is triggered in the reactive redis session repository in the apply() method at the place I mention in my original post:
Hope this sample project helps to find out if its a bug or an implementation problem on my side.
This is a bug in Spring Session's SpringSessionWebSessionStore, or more specifically, its internal WebSession implementation. The problem is that on WebSession#invalidate session is only cleared from the underlying session store, but not marked as invalid so the subsequent request processing still ends up using the same session id.
I've openend gh-1114 to address this in Spring Session.
I believe you haven't experienced this previously i.e. with Spring Session 2.0.2.RELEASE and below due to another bug in SpringSessionWebSessionStore that was addressed in 2.0.3.RELEASE - see gh-1039. This issue was about failing to update the lastAccessedTime, and once we fixed that the scenario you described started failing due to session for invalidated (and deleted) session id being saved again only with lastAccessedTime attribute.

How to call a method before the session object is destroyed?

When developing a JSP application it's possible to define a session timeout value, say 30 minutes.
After that timeout, the session object is destroyed.
Moreover I can programmatically invalidate a session calling session.invalidate() .
Since I'm saving a complex Java object inside the HTTP session, before invalidate the session or let it expire by the tomcat app server, I need to call a saved object method to release some memory. Of course I can do it programmatically when the user click a logout button.
What I would like to do is intercepting the Tomcat app server when it is going to destroy all expired sessions (30 minutes or custom), so that I can pre-process Java objects saved in the session calling a specific method to release memory.
Is it possible?
Yes, that's possible. You could use HttpSessionListener and do the job in sessionDestroyed() method,
#WebListener
public class MyHttpSessionListener implements HttpSessionListener {
#Override
public void sessionDestroyed(HttpSessionEvent event) {
// Do here the job.
}
// ...
}
Or you could let the complex object which is been stored as a session attribute implement the HttpSessionBindingListener and do the job in valueUnbound() method.
public class YourComplexObject implements HttpSessionBindingListener {
#Override
public void valueUnbound(HttpSessionBindingEvent event) {
// Do here the job.
}
// ...
}
It will be called whenever the object is to be removed from the session (either explicitly by HttpSession#removeAttribute() or by an invalidation/expire of the session).

Access HTTP Session from Spring Security Login Process

Can I access the HTTP Session object from within the retrieveUser method of my class which extends org.springframework.security.providers.dao.AbstractUserDetailsAuthenticationProvider
If so, how? Here is the method signature for retrieveUser:
public UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication);
I'm trying to place some user information on the Session after the user logs in.....
How about using a session-scoped bean for whatever you need to store and an ApplicationListener that populates it? You can define the listener as (assuming you use Java configuration):
#Bean
public ApplicationListener<AbstractAuthenticationEvent> authenticationListener() {
return new MyAuthenticationListener();
}
Well, my solution was to add the data to the UserDetails object returned by the retrieveUser object. I could then use Spring Security to retrieve the data from anywhere in the application.

Categories

Resources