Streetsouls
Streetsouls

Reputation: 245

Spring Security logout does not work - does not clear security context and authenticated user still exists

I know, there are many articles about this topic, but I have a problem and I can't find any solution.

I have a classic spring security java config:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private AuctionAuthenticationProvider auctionAuthenticationProvider;

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(auctionAuthenticationProvider);
}

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.httpBasic();

    ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequest = http.authorizeRequests();

    configureAdminPanelAccess(authorizeRequest);
    configureFrontApplicationAccess(authorizeRequest);
    configureCommonAccess(authorizeRequest);

    http.csrf()
        .csrfTokenRepository(csrfTokenRepository()).and()
        .addFilterAfter(csrfHeaderFilter(), CsrfFilter.class);

    http.logout()
        .clearAuthentication(true)
        .invalidateHttpSession(true);
}
...
}

Also, I have two controller methods, where I login/logout from my web application by AJAX.

When I would like to logout, I first call this method, which I expect to clear user sessions and clear everything from the security context.

@Override
@RequestMapping(value = "/logout", method = GET, produces = APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<Boolean> logout(final HttpServletRequest request, final HttpServletResponse response) {
    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    if (auth != null){
        new SecurityContextLogoutHandler().logout(request, response, auth);
    }

    return new ResponseEntity<>(Boolean.TRUE, HttpStatus.OK);
}

After this I reload my client web application and each time, when it is reloaded, I check whether the user is authenticated by calling the following controller method:

@Override
@RequestMapping(value = "/user", method = GET, produces = APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<UserDetails> user() {
    Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    if (principal instanceof UserDetails) {
        return new ResponseEntity<>((UserDetails) principal, HttpStatus.OK);
    }

    return null;
}

And here I aways receive the last authenticated user. It seems that in the previous logout method, Spring logout doesn't work.

Keep in mind that I tried to logout with the following code, without any success:

   @Override
   @RequestMapping(value = "/logout", method = GET, produces = APPLICATION_JSON_UTF8_VALUE)
    public ResponseEntity<Boolean> logout(final HttpServletRequest request) {
     try {
         request.logout();

         return new ResponseEntity<>(Boolean.TRUE, HttpStatus.OK);
     } catch (ServletException ex) {
         if (LOG.isDebugEnabled()) {
             LOG.debug("There is a problem with the logout of the user", ex);
         }
    }

Are you have any idea what I miss in my config and the logout process?

Upvotes: 19

Views: 54948

Answers (5)

FreezY
FreezY

Reputation: 1671

From your question, I see you are trying to create your own logout and you also trying to use the default Spring logout. I advise that you should choose one method and not mix them both. There are two I recommend to logout from Spring:

First: Default spring security logout

.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/logout.done").deleteCookies("JSESSIONID")
.invalidateHttpSession(true) 

From the example above, you should only need to call the /logout URL whenever you want to logout the user. No need to create any @Controller to handle that logout instead Spring will help to log the user out. You also can add other thing you want to invalidate here.

Second: Programmatically logout

@RequestMapping(value = {"/logout"}, method = RequestMethod.POST)
public String logoutDo(HttpServletRequest request,HttpServletResponse response){
HttpSession session= request.getSession(false);
    SecurityContextHolder.clearContext();
         session= request.getSession(false);
        if(session != null) {
            session.invalidate();
        }
        for(Cookie cookie : request.getCookies()) {
            cookie.setMaxAge(0);
        }

    return "logout";
}

If you are using this logout approach, you don't need to include the first method in ht eSpring security config. By using this method, you can add an extra action to perform before and after logout done. BTW, to use this logout, just call the /logout url and the user will be logged out manually. This method will invalidate the session, clear Spring security context and cookies.

In addition for the second method, if you are using RequestMethod.POST, you need to include the CSRF key on the POST request. The alternative way is to create a form with a hidden input CSRF key. This is some example of auto generated logout link with jQuery :

$("#Logout").click(function(){
    $form=$("<form>").attr({"action":"${pageContext.request.contextPath}"+"/logout","method":"post"})
    .append($("<input>").attr({"type":"hidden","name":"${_csrf.parameterName}","value":"${_csrf.token}"}))
    $("#Logout").append($form);
    $form.submit();
});

You just need to create a hyperlink <a id="Logout">Logout</a> to use it.

If you are using RequestMethod.GET,just include a CSRF key as a parameter in you link like this:

<a href="${pageContext.request.contextPath}/logout?${_csrf.parameterName}=${_csrf.token}">Logout</a>

Thats all, hope it helps.

Upvotes: 52

Yogesh Shipkule
Yogesh Shipkule

Reputation: 1

enter image description here

Just change logout URL from "/logout" to "war or snapshot name/logout"

Upvotes: 0

Kaique Dias
Kaique Dias

Reputation: 45

I solved my problem similarly by adding the following parameter to the application.properties file

spring.cache.type=NONE

Upvotes: 2

Raf
Raf

Reputation: 7649

Just a heads up, there is Clear Site Data HTTP header as shown below

Clear-Site-Data: "cache", "cookies", "storage", "executionContexts"

I also helped add support for Clear-Site-Data header into Spring-Security 5.2 project. For more details around the implementation, see the PR.

Here is a sample of how it is going to work

@EnableWebSecurity
static class HttpLogoutConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .logout()
                    .addLogoutHandler(new HeaderWriterLogoutHandler(
                           new ClearSiteDataHeaderWriter(SOURCE)));
    }
}

Where SOURCE is a vararg of one or more of the following

  • "*" Clear everything
  • One or more of "cache", "cookies", "storage", "executionContexts"

For more details see the sample test in the LogoutConfigurerClearSiteDataTests.java.

Upvotes: 3

Laci
Laci

Reputation: 41

This will help, i think clearAuthentication(true) is enough:

@Configuration 
@EnableWebSecurity 
public class SecurityConfig extends WebSecurityConfigurerAdapter {

....

    @Override
    protected void configure(HttpSecurity http) throws Exception
    {
        http
        .httpBasic()
        .and()
        .logout().clearAuthentication(true)
        .logoutSuccessUrl("/")
        .deleteCookies("JSESSIONID")
        .invalidateHttpSession(true)
        .and()

Upvotes: 2

Related Questions