2

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.

THE Waterfall
  • 645
  • 6
  • 17
  • Can you share your security config ? – chaoluo Jan 09 '19 at 09:03
  • 2
    Don't use a controller. Configure Spring Security to allow to logout with a GET request. See https://stackoverflow.com/questions/20333176/spring-security-3-2-0rc2-logout-url-post-only for how to do that. – M. Deinum Jan 09 '19 at 10:20

2 Answers2

2

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)

Andrew Sasha
  • 1,254
  • 1
  • 11
  • 21
  • Suggesting to make the whole application less secure to get a logout function working with a GET is not really a wise thing to do. – M. Deinum Jan 09 '19 at 13:54
  • @M.Deinum I feel like an idea to use a GET to log out is not a good idea at all, is it? Wouldn't it be easier and better just to use a POST request but with the help of JS to make it look like a common link? – THE Waterfall Jan 09 '19 at 14:03
  • Using a GET has some security risks. But you can do a regular form post you don't really need JS for that. – M. Deinum Jan 09 '19 at 14:09
  • @THEWaterfall is right disabling csrf has some [security risk](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)), the `post` is safer. Don't implement your own controller, spring security gives you [all needed features](https://www.baeldung.com/spring-security-logout) (even by default). – Andrew Sasha Jan 10 '19 at 09:18
2

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.

THE Waterfall
  • 645
  • 6
  • 17