Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does anonymous user get redirected to expiredsessionurl by Spring Security

I'm really trying to understand how Spring Security works, but I'm a bit lost at the moment. Here's the simple scenario:

  1. User visits the website home page but doesn't log in
  2. SecurityContextPersistenceFilter logs that no SecurityContext was available and a new one will be created
  3. AnonymousAuthenticationFilter populates SecurityContextHolder with an anonymous token
  4. A session is created with ID = C2A35ED5A41E29865FF53162B0024D52
  5. User lets the page sit idle until the session times out
  6. User clicks on the About page (or home page again)
  7. SecurityContextPersistenceFilter again logs that no SecurityContext was available and a new one will be created
  8. AnonymousAuthenticationFilter again populates SecurityContextHolder with an anonymous token
  9. SessionManagementFilter logs that requested session ID C2A35ED5A41E29865FF53162B0024D52 is invalid
  10. SessionManagementFilter logs that it is starting a new session and redirecting to /invalidsession

These pages are configured to .authorizeRequests().antMatchers("/","/home","/about").permitAll(). I have the invalid session option turned on to handle authenticated users: .sessionManagement().invalidSessionUrl("/errors/invalidSession"). If I comment out that option, then everything described above is exactly the same EXCEPT for step #10 - SessionManagementFilter sees that the requested session ID is invalid (#9) but does NOT start a new session and perform the redirect (#10).

WHY? What can I do to keep the invalid session option but correctly handle anonymous users, i.e., not be redirected? Or is that just not possible and I'll have to handle authenticated users separately? I'd be very grateful if anyone can help me understand what's happening here and point me in a direction to solve this. Let me know if you need to see my full http configuration.

EDIT

I ran a series of tests with anonymous and registered (authenticated) users. If .sessionManagement().invalidSessionUrl("/errors/invalidSession") is enabled then both types of users will eventually arrive at the error page. Authenticated users with RememberMe unchecked are the same as anon users. If RememberMe is checked, then the error page appears once RememberMe times out.

If I disable the invalid session option, no users ever get the error page (which makes sense). Both types of users can browse public pages as long as they want and authenticated users will be asked to log in after the session or RememberMe expires.

If you're interested the code involved here is in SessionManagementFilter

if (invalidSessionStrategy != null) {
    invalidSessionStrategy
        .onInvalidSessionDetected(request, response);
    return;
}

If .sessionManagement().invalidSessionUrl is enabled the default method SimpleRedirectInvalidSessionStrategy is called, which executes this piece of code:

if (createNewSession) {
    request.getSession();
}
redirectStrategy.sendRedirect(request, response, destinationUrl);

The createNewSession boolean can be set through setCreateNewSession(boolean createNewSession), which is described as:

Determines whether a new session should be created before redirecting (to avoid possible looping issues where the same session ID is sent with the redirected request). Alternatively, ensure that the configured URL does not pass through the SessionManagementFilter.

So, it looks to me like .sessionManagement().invalidSessionUrl works best for sites where all pages are authenticated. The options I'm looking at are a custom filter placed before the SessionManagementFilter that checks the page access and turns 'createNewSession' on/off as needed or turning off the invalid session option and handling it elsewhere for authenticated pages (?). I also stumbled across <%@ page session=“false” %> in this SO question - Why set a JSP page session = “false” directive? - which I'm going to look into further. Being so new to Spring Security I don't have a good sense of the best practice for handling this situation correctly. Any help would be appreciated.

like image 971
LWK69 Avatar asked Sep 24 '15 16:09

LWK69


People also ask

Why is the anonymous user authenticated in Spring Security?

Spring Security's anonymous authentication just gives you a more convenient way to configure your access-control attributes. Calls to servlet API calls such as getCallerPrincipal , for example, will still return null even though there is actually an anonymous authentication object in the SecurityContextHolder .

Why does the session ID change when I authenticate through Spring Security?

Why does the session Id change when I authenticate through Spring Security? With the default configuration, Spring Security invalidates the existing session when the user authenticates and creates a new one, transferring the session data to it.

How do I redirect Spring Security?

The most common ways to implement redirection logic after login are: using HTTP Referer header. saving the original request in the session. appending original URL to the redirected login URL.

How do I invalidate a spring boot session?

Configure the Session Timeout With Spring Boot. If we don't specify the duration unit, Spring will assume it's seconds. In a nutshell, with this configuration, the session will expire after 15 minutes of inactivity. The session is considered invalid after this period of time.


1 Answers

OK, so I've spent the last couple of weeks digging around in Spring Security trying to understand how it all fits together. I'm still learning, but for this particular situation I found two approaches that work.

The obvious one is to just bypass security for public pages like this:

@Override
public void configure(WebSecurity web) throws Exception
{
    web
    .ignoring()
        .antMatchers("/", "/home", "/about", "/login**", "/thankyou", "/user/signup**", "/resources/**")
    ;
}

I still don't know enough about web security in general to know if this is an acceptable approach or not, but it allows anonymous users to browse the site w/o ever getting an invalid session error.

The harder solution (for a Java and Spring noob like me) is based upon these SO questions:

Spring security invalid session redirect

How to set a custom invalid session strategy in Spring Security

The default SimpleRedirectInvalidSessionStrategy class is final which meant I had to create basically a copy of that class (not sure how good an idea that is). You can't use a session attribute because the session has been destroyed by the time it gets to this strategy so I created a helper class for a session cookie called authUser (I can post the class if anyone wants to see it). The cookie is created or updated in the LoginSuccessHandler or RememberMeSuccessHandler and it indicates if the user is anonymous or authenticated:

authCookie.setCookie(request, response, "anonymousUser");
or
authCookie.setCookie(request, response, authentication.getName());

I'm currently using the actual login only for testing purposes - it will ultimately be just a simple yes/no indicator of some sort. CustomLogoutSuccessHandler resets it to anonymousUser

The invalid session method looks like this:

@Override
public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response) 
    throws IOException, ServletException {

    String url = destinationUrl;

    //reset context default value
    redirectStrategy.setContextRelative(false);

    if (authCookie.isCurrentCookieAnonymous()) {
        //pass the URL originally requested by the anonymous user
        url = request.getRequestURI();
        //the URL needs to have the context removed
        redirectStrategy.setContextRelative(true);
    }

    //always revert to anonymous user
    authCookie.setCookie(request, response, "anonymousUser");

    logger.debug("Starting new session (if required) and redirecting to '" + url + "'");

    if (createNewSession)
        request.getSession();

    redirectStrategy.sendRedirect(request, response, url);
}

Again, I can post the full class if requested.

The SecurityConfig class includes the following:

@Bean
public SessionManagementBeanPostProcessor sessionManagementBeanPostProcessor() {
    return new SessionManagementBeanPostProcessor();
}

protected static class SessionManagementBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        if (bean instanceof SessionManagementFilter) {
            SessionManagementFilter filter = (SessionManagementFilter) bean;
            filter.setInvalidSessionStrategy(new RedirectInvalidSession("/errors/invalidSession"));
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        return bean;
    }
}

My testing so far has been successful for both anonymous and authenticated users, but this approach has not been production tested.

like image 132
LWK69 Avatar answered Oct 19 '22 02:10

LWK69