3

Fisrt, I need to say that I'm using session scoped bean. So before session is closed the preDestroy() method is invoked

@Component
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS, value = "session")
public class MySessionBean {

    @PreDestroy
    public void preDestroy() {

        //Do Smth with using Security principal

    }
}

When I logout by using Spring Security utils everything goes fine, the preDestroy() method is called.

The main problems come when I use

server.session-timeout = 60 or = 1 in application.properties

  1. preDestroy() is called approximately in 2.5 minutes after session has opened.
  2. And much more interesting is that SecurityContextHolder.getContext().getAuthentication().getPrincipal(); is null.
    BUT I've successfully loged out.

Also I've tried a

@Bean
public EmbeddedServletContainerCustomizer servletContainerCustomizer() {
    return (ConfigurableEmbeddedServletContainer configurableEmbeddedServletContainer) -> 
        configurableEmbeddedServletContainer.setSessionTimeout(1, TimeUnit.MINUTES);
    }

I have the same result.
Also the problem exists while using Provided Tomcat

UPDATE:

The weird thing is that if I manually after 1 minute check the session existence the method preDestroy() is called immediately. But Security Principal is already null


Thanks in Advance!

InsFi
  • 1,298
  • 3
  • 14
  • 29
  • 1
    Nothing strange there... There is a thread which check about every x seconds if sessions are invalid. So when your set your timeout to 1 minuten it is 1 minuten + a bit more before your sessions is actually cleared. When you check the session yourself, the invalid session is already cleaned as then it it is forcefully checked. – M. Deinum Sep 15 '15 at 12:24
  • @M.Deinum , ok I can live with that. But what I need to do to have `Security Principal` 'alive' in triggered method? As I understand you, server kills session after 1 minute and makes also session not valid. So when @PreDestroy method is invoked there is no `Security Principal` left – InsFi Sep 15 '15 at 12:36
  • Why wouldn't it be null, the session isn't there anymore neither are your attributes in the session. – M. Deinum Sep 15 '15 at 12:41
  • @M.Deinum , so the only way to get `Principal` in @PreDestroy method is to create `private field` without getter / setter and instantiate it in @PostConstruct method? But the main problem is that I call method which is secured. Should I make it unsecure or write some duplicate code only for this situation? – InsFi Sep 15 '15 at 12:56
  • Don't write that code... Create an `ApplicationListener` to get a trigger when an event is destroyed. This contains the session and should also contain the principal. The way you are doing it will always result in `null` because when a session timeout there are no Spring Security filters invoked and as such no thread locals will be set. – M. Deinum Sep 15 '15 at 13:07
  • @M.Deinum , I've implemented `ApplicationListener` and have exactly the same situation. – InsFi Sep 15 '15 at 14:42
  • @M.Deinum , and `HttpSessionListener` implementations doesn't help – InsFi Sep 15 '15 at 15:01
  • @M.Deinum , seems like problem in `SpringContextHolder`. `session.getAttribute("SPRING_SECURITY_CONTEXT")` is not `null`. – InsFi Sep 15 '15 at 15:47
  • Because still Spring Security filters aren't invoked on a session invalidation. Instead of doing `SecurityContextHolder.getContext()` as that will always return null for the principal because it weill NEVER be filled on a timeout. The `HttpSessionDestroyedEvent` has a method `getSecurityContexts` which contains the `SecurityContext` which in turn contain the `Authentication`. Instead of just copy-pasting you might want to read the documentation... – M. Deinum Sep 15 '15 at 18:45
  • @M.Deinum , please read my approach to solve that(answer). If it doesn't fit correct me(or / and I delete my post) to let you give more accurate answer – InsFi Sep 15 '15 at 18:50
  • See my comment and answer. – M. Deinum Sep 15 '15 at 18:51

2 Answers2

3

When a session does timeout the SecurityContextHolder.getContext().getAuthentication().getPrincipal() will always return null. The SecurityContext is only filled when a request comes in, one of the filters does that. When a session times out the filters will of course not be invoked and as such the SecurityContext not filled.

Instead create a bean that implements ApplicationListener<HttpSessionDestroyedEvent>. The HttpSessionDestroyedEvent has a method getSecurityContexts that returns the SecurityContexts as originally in the HttpSession.

public class YourListener implements ApplicationListener<HttpSessionDestroyedEvent> {

    public void onApplicationEvent(HttpSessionDestroyedEvent evt) {
        for (SecurityContext ctx : evt.getSecurityContexts() ) {
             Authentication auth = ctx.getAuthentication();
             Object principal = auth.getPrincipal();
             // Do your thing with the principal.
        }
    }
}
M. Deinum
  • 115,695
  • 22
  • 220
  • 224
  • Is there significant difference between `getSecurityContexts` by impelenting Application listener and `session.getAttribute ("SPRING_SECURITY_CONTEXT")? – InsFi Sep 15 '15 at 19:18
  • If for some reason it is decided to change the attribute name or way of storing this will still work, your solution would brake. Also you could use dependency injection in the application listener as it is a regular spring bean. – M. Deinum Sep 15 '15 at 19:27
1

As M. Deinum said:

There is a thread which check about every x seconds if sessions are invalid. So when your set your timeout to 1 minute it is 1 minute + a bit more before your sessions is actually cleared. When you check the session yourself, the invalid session is already cleaned as then it it is forcefully checked.

So delay of preDestroy() invocation has been explained.

The next problem was how to get Security Principal after SESSION-TIMEOUT

NOTE that by implementing

  • ApplicationListener<HttpSessionDestroyedEvent>
  • HttpSessionListener
  • Session scope bean

    the

SecurityContextHolder.getContext().getAuthentication() == null when appropriate destroy method is called

To get principal visit relatedStackPost

After you'll do that implement HttpSessionListener

@Component
public class MySessionListener implements HttpSessionListener {

    @Override
    public void sessionCreated(HttpSessionEvent httpSessionEvent) {
        ...
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
        HttpSession httpSession = httpSessionEvent.getSession();
        SecurityContext securityContext = (SecurityContextImpl) httpSession.getAttribute("SPRING_SECURITY_CONTEXT");

    }
}
Community
  • 1
  • 1
InsFi
  • 1,298
  • 3
  • 14
  • 29