ar.thewaterfall
ar.thewaterfall

Reputation: 835

How to invalidate remember-me on logout?

Well, I do not implement PersistentTokenBasedRememberMeServices therefore I cannot use .logout(request, response, auth). But I use JdbcTokenRepositoryImpl in order to use PersistentTokenRepository for remember-me feature.

LogoutController:

@Controller
public class LogoutController {

    @RequestMapping(value = {"/logout"}, method = RequestMethod.GET)
    public String logout() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();

        if(auth != null) {
            SecurityContextHolder.getContext().setAuthentication(null);
        }

        return "redirect:/login?logout";
    }
}

Security config:

@Configuration
@EnableWebSecurity
public class AppSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authProvider());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/", "/playground").hasAnyRole("ROOT", "MODER", "USER")
            .antMatchers("/users/**").hasAnyRole("ROOT", "MODER")
        .and()
            .formLogin().loginPage("/login").loginProcessingUrl("/login").failureHandler(customAuthenticationFailureHandler())
        .and()
            .rememberMe().rememberMeParameter("remember-me").tokenRepository(persistentTokenRepository()).userDetailsService(userDetailsService)
        .and()
            .logout().logoutUrl("/logout");
    }

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public DaoAuthenticationProvider authProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setPasswordEncoder(passwordEncoder());
        authProvider.setUserDetailsService(userDetailsService);
        return authProvider;
    }

    @Bean
    public AuthenticationFailureHandler customAuthenticationFailureHandler() {
        return new CustomAuthenticationFailureHandler();
    }

    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        return jdbcTokenRepository;
    }

When I log in with remember-me, I cannot log out then. I guess because of remember-me feature. What should I add to LogoutController to make a proper logout proccess?

Note: the thing is, that if I just use POST method on logout, then it perfectly works, but I'd like to use GET method and thus I have to create a logout controller to perform get method.

Upvotes: 2

Views: 1378

Answers (2)

ar.thewaterfall
ar.thewaterfall

Reputation: 835

To sum it up.

I've managed to test several ways and here what I have got:

  1. As M. Deinum suggested in comments, it's possible not to use a controller and nevertheless have a logout with a GET request. Here it is.

.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout"))

  1. As Andrew Sasha suggested in the answer section, you can disable csrf as it intentionally prevents using GET request. And now you can use a GET request to log out without even using any controller.

http.csrf().disable()

  1. If you still wish to use the controller, none of the following things will help you

.deleteCookies("remember-me", "JSESSIONID")
.invalidateHttpSession(true)
.clearAuthentication(true)
.logoutSuccessUrl("/")

(I'm not sure, but I feel like it doesn't work because you perform a GET request and use your controller to control logout)

  1. So then you can do it programmatically

First, you add a name for remember me cookie in Spring security config:

rememberMe().rememberMeCookieName("remember-me")

And then in logout controller add this:

String cookieName = "remember-me";
Cookie cookie = new Cookie(cookieName, null);
cookie.setMaxAge(0);
cookie.setPath(StringUtils.hasLength(request.getContextPath()) ? request.getContextPath() : "/");
response.addCookie(cookie);

The only problem here is that you have to delete a record from persistent_logins table manually

(In order to get a request and a response you just pass them into a method public void logout(HttpServletRequest request, HttpServletResponse response)

  1. It is possible to use a POST request but use it as a link with the help of JavaScript or even plain HTML and CSS.

The solutions for that you can find on this topic.

So what do we have here?

Summarizing everything above I can say that if you want a controller, you have to programmatically write everything yourself (someone would tell that it's a reinventing of a wheel).

Still, it is possible to use a GET request but without controller which is described in the 1st and 2nd positions of the list.

(The consequences of using a GET request is written within CSRF Documentation and it does not recommend to use a GET request because of its invulnerability.)

So the last thing that I decided to be my favorite is to make a POST request look like a GET request (use it as a link) with the help of JS or HTML and CSS. And as you use a POST request you kind of have a CSRF protection.

I hope this will help someone.

Upvotes: 2

Andrew Sasha
Andrew Sasha

Reputation: 1294

Try to disable crsf (http.csrf().disable()).

The default implementation in spring's security Logout filter is:

        if (http.getConfigurer(CsrfConfigurer.class) != null) {
            this.logoutRequestMatcher = new AntPathRequestMatcher(this.logoutUrl, "POST");
        }
        else {
            this.logoutRequestMatcher = new OrRequestMatcher(
                new AntPathRequestMatcher(this.logoutUrl, "GET"),
                new AntPathRequestMatcher(this.logoutUrl, "POST"),
                new AntPathRequestMatcher(this.logoutUrl, "PUT"),
                new AntPathRequestMatcher(this.logoutUrl, "DELETE")
            );
        }

as you can see if your Csrf is enabled (by default it's enabled even if you overwride protected void configure(HttpSecurity http)) then only POST method will be work, if not all are working.

BTW: Are you sure your request reaching the LogoutController, because I thing it's uses standard spring security logout mechanism? (To disable it do http.logout().disable(), the same as csrf it's enabled by default)

Upvotes: 2

Related Questions