Spring security invalid session redirect

17.4k Views Asked by At

I'm using spring security 4.0.1 inside a spring boot 1.2.3 web application ( and also with spring-session 1.0.1, but this is irrelevant for the case ).

I do have a private area, and an all access area ( "/about", "/","/contact",... more than 20 pages ) for which every user can access. ( it's like a web-shop )

Whenever a logged-in user session expires,Spring detects an invalid session and redirects the user to the '.invalidSessionUrl("/session/error/invalid")'

However, i only want to be redirected if the target link in inside the private area, nor the public one.

How can i avoid that ?

Thanks.

This is my (java) config : ( updated after seen post )

 http
            .authorizeRequests()
            .anyRequest()
                .permitAll()
            .antMatchers("/privado/**")
                .authenticated()
            .and()
                .formLogin()
                .loginPage("/login")
                .failureUrl("/login?error")
                .defaultSuccessUrl("/")
                .successHandler(new SessionSuccessHandler())
            .and()
                .logout()
                .logoutSuccessUrl("/")
                .deleteCookies("JSESSIONID", "SESSION")
            .and()
                .sessionManagement()
                .invalidSessionUrl("/session/error/invalid")
            .sessionFixation()
            .changeSessionId()
            .maximumSessions(1)
            .expiredUrl("/session/error/expired")
            .and()
            .and()
                .csrf()
                .ignoringAntMatchers("/jolokia/**", "/v1.0/**");

How can i achieve that ?

Thanks a lot.

3

There are 3 best solutions below

1
On

You can provide a custom SessionAuthenticationStrategy to do this. For example:

public class MatcherSessionAuthenticationStrategy implements SessionAuthenticationStrategy {

    private final SessionAuthenticationStrategy delegate;

    private final RequestMatcher matcher;

    public MatcherSessionAuthenticationStrategy(
            SessionAuthenticationStrategy delegate, RequestMatcher matcher) {
        super();
        this.delegate = delegate;
        this.matcher = matcher;
    }

    public void onAuthentication(Authentication authentication,
            HttpServletRequest request, HttpServletResponse response)
            throws SessionAuthenticationException {
        if(matcher.matches(request)) {
            delegate.onAuthentication(authentication, request, response);
        }
    }
}

Then you can inject a RequestMatcher and the ConcurrentSessionControlAuthenticationStrategy into the class. The easiest way to configure this would be to create a BeanPostProcessor:

public class ConcurrentSessionControlAuthenticationStrategyBeanPostProcessor
        implements BeanPostProcessor {

    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {
        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName)
            throws BeansException {
        if(!(bean instanceof CompositeSessionAuthenticationStrategy)) {
            return bean;
        }

        RequestMatcher matcher = antMatchers("/about", "/","/contact");
        SessionAuthenticationStrategy original = (SessionAuthenticationStrategy) bean;
        return new MatcherSessionAuthenticationStrategy(original, matcher);
    }

    /**
     * Create a {@link List} of {@link AntPathRequestMatcher} instances.
     *
     * @param httpMethod the {@link HttpMethod} to use or {@code null} for any
     * {@link HttpMethod}.
     * @param antPatterns the ant patterns to create {@link AntPathRequestMatcher}
     * from
     *
     * @return an OrRequestMatcher with a {@link List} of {@link AntPathRequestMatcher} instances
     */
    public static RequestMatcher antMatchers(
            String... antPatterns) {
        List<RequestMatcher> matchers = new ArrayList<RequestMatcher>();
        for (String pattern : antPatterns) {
            matchers.add(new AntPathRequestMatcher(pattern));
        }
        return new OrRequestMatcher(matchers);
    }
}

You can then add the following to your configuration:

@Bean
public static BeanPostProcessor sessionBeanPostProcessor() {
    return new ConcurrentSessionControlAuthenticationStrategyBeanPostProcessor();
}

It is important to use a static method since this is a BeanPostProcessor which will need to be initialized early on.

PS I'd consider formatting your configuration as outlined in this blog

0
On

@RobWinch - This seem like a pretty common use case and the solution you propose does not seem to work from the test I ran and also comments. Similar issue was raised I believe in http://forum.spring.io/forum/spring-projects/security/94772-redirect-to-invalid-session-url-only-when-user-accesses-secured-resource and it appears it was never resolved. My thinking is to have multiple http settings (using xml config)

<http pattern="/aboutUs**" security="none" />
<http pattern="/contact**" security="none" />
etc

This does not seem ideal when having quite a number unsecured pages and also adding a new unsecured page requires a configuration update. It will be nice if we can have an "ideal" solution for this use case. With Spring security 4.1 release, it appears there is still no clear way to do this.

0
On

Another workaround which helped me deal with this issue in similar situation to yours is having an Expired/Invalid session strategy added to your configuration like so:

http
    .expiredSessionStrategy(e -> {
        handleExpiredInvalidSessions(e.getRequest(), e.getResponse());
    })
    .sessionRegistry(sessionRegistry())
    .and()
    .invalidSessionStrategy((request, response) -> {
        handleExpiredInvalidSessions(request, response);
    })

Then you will implement it to match the public URIs and simply forward the request

private void handleExpiredInvalidSessions(HttpServletRequest request, HttpServletResponse response) {
    String requestUri = request.getRequestURI();
    if (isPublicURI(requestUri)) {
        // This will remove the invalid/expired session from the request
        // and prevent the request from failing again
        request.getSession(true).invalidate();
        RequestDispatcher dispatcher = request.getRequestDispatcher(requestUri);
        // Retry the request
        dispatcher.forward(request, response);
    } else {
        // might redirect if you wish
        response.setStatus(440);
    }
}

You still need to implement isPublicURI() depending on your desired public paths, in my case it was only one path so it was quite easy.