javaspringspring-securityspring-java-config

Redirecting from /login to / for authenticated users


We have a Spring Security app that mostly just handles authentication via a Thymeleaf page before passing the user to an Angular application. Currently if a user has already logged in tries going to /login, it takes them to the login page to be filled out - if they go to just / then they'll be properly redirected to our dashboard/homepage. We just want to be able to redirect /login to / for authenticated users.

Here's the HttpSecurity configuration we're using that I'm guessing should only require a simple modification to achieve this goal:

@Override
public void configure( HttpSecurity http ) throws Exception
{
    // @formatter:off
    http
            .cors()
            .and()
            .exceptionHandling()
            .accessDeniedHandler( accessDeniedHandler() )
            .authenticationEntryPoint( new RestAuthenticationEntryPoint() )
            .and()
            .authorizeRequests()
            .antMatchers( "/login" ).permitAll()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .loginPage( "/login" )
            .failureHandler( internalAuthFailureHandler )
            .successHandler( internalAuthSuccessHandler )
            .permitAll()
            .and()
            .logout()
            .logoutUrl( "/logout" )
            .logoutSuccessUrl( "/login?logout" )
            .permitAll()
            .and()
            .csrf()
            .csrfTokenRepository( CookieCsrfTokenRepository.withHttpOnlyFalse() );
    // @formatter:on
}

I tried using .not().authenticated() for the /login page, but couldn't figure that out and I don't understand what documentation is supposed to help with this.


Solution

  • This is because the filter that generates the login page is higher than the filter that processes the HttpSecurity matchers, hence why using .not().authenticated() or other conventional methods did not work for you.

    First, I created a custom filter class, I named it LoginPageFilter:

    class LoginPageFilter extends GenericFilterBean {
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            if (SecurityContextHolder.getContext().getAuthentication() != null
                  && SecurityContextHolder.getContext().getAuthentication().isAuthenticated()
                  && ((HttpServletRequest)request).getRequestURI().equals("/login")) {
                System.out.println("user is authenticated but trying to access login page, redirecting to /");
                ((HttpServletResponse)response).sendRedirect("/");
            }
            chain.doFilter(request, response);
        }
    }
    

    The above will check if the current user is authenticated and if the current path is /login.

    If he is, then it will redirect him to /. Otherwise, continue going through the filter chain.

    After, you need to add the filter to the existing filter chain, but you can't add it anywhere. It has to be after the authentication has been resolved by the session id (otherwise, SecurityContextHolder.getContext().getAuthentication() would always return null) and it has to be before the existing filter than permits access to the login page.

    The best candidate is before DefaultLoginPageGeneratingFilter, which is the filter generating the login page.

    To add the custom filter at that position, you need to add this at the top of your configure(HttpSecurity http) method:

    http.addFilterBefore(new LoginPageFilter(), DefaultLoginPageGeneratingFilter.class);
    

    In your example, it would look like the following:

    @Override
    public void configure( HttpSecurity http ) throws Exception
    {
        http.addFilterBefore(new LoginPageFilter(), DefaultLoginPageGeneratingFilter.class);
        // @formatter:off
        http
                .cors()
                .and()
                .exceptionHandling()
                .accessDeniedHandler( accessDeniedHandler() )
                .authenticationEntryPoint( new RestAuthenticationEntryPoint() )
                .and()
                .authorizeRequests()
                .antMatchers( "/login" ).permitAll()
                // ...
        // @formatter:on
    }
    

    I've uploaded my complete class in case you need to have a closer look at it:

    https://gist.github.com/TwinProduction/5e2e320f5f87c009c330828c3e34a6dc

    Good luck!

    Update

    At @dur's suggestion, I replaced UsernamePasswordAuthenticationFilter for DefaultLoginPageGeneratingFilter, which is much more appropriate candidate.