Search code examples
javaspringspring-securityidentityserver4

Spring Security: How to revoke a refresh token on log-out?


I have been searching around and in many SO answers, Git issues, etc. I can't find a way to revoke a refresh token on the user's logout using only Spring Security and no the deprecated "Spring Security OAuth" project.

Is there a way to manage this automatically and not making a request manually to the IdP by myself?

What I would like to do is revoke the token using the IdP endpoint of revocation, following the RFC7009 after performing logout in order to avoid this refresh token to being used out-of-context and for security reasons.


Solution

  • After opening an issue in GitHub about the current support of the RFC7009 and automatic token revocation in Spring Security, I have implemented a recommended solution by using one custom LogoutHandler:

    I have created the following class in my project (which implements LogoutHandler):

    CustomLogoutHandler.java

    package com.test.demo.authorization;
    
    import org.springframework.core.env.Environment;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
    import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
    import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
    import org.springframework.security.web.authentication.logout.LogoutHandler;
    import org.springframework.stereotype.Service;
    import org.springframework.util.LinkedMultiValueMap;
    import org.springframework.web.reactive.function.BodyInserters;
    import org.springframework.web.reactive.function.client.WebClient;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    @Service
    public class CustomLogoutHandler implements LogoutHandler {
    
        private final Environment env;
        private final OAuth2AuthorizedClientService authorizedClientService;
    
        public CustomLogoutHandler(Environment env, OAuth2AuthorizedClientService authorizedClientService) {
            this.env = env;
            this.authorizedClientService = authorizedClientService;
        }
    
        @Override
        public void logout(HttpServletRequest request, HttpServletResponse response,
                           Authentication authentication) {
            String clientRegistrationId = ((OAuth2AuthenticationToken) authentication).getAuthorizedClientRegistrationId();
            OAuth2AuthorizedClient authorizedClient = this.authorizedClientService.loadAuthorizedClient(clientRegistrationId, authentication.getName());
            revokeRefreshToken(authorizedClient.getRefreshToken().getTokenValue());
        }
    
        private void revokeRefreshToken(String refreshToken) {
            String revocationEndpoint = env.getProperty("spring.security.oauth2.client.registration.test.revocation-endpoint");
            String clientId = env.getProperty("spring.security.oauth2.client.registration.test.client-id");
            String clientSecret = env.getProperty("spring.security.oauth2.client.registration.test.client-secret");
    
            LinkedMultiValueMap map = new LinkedMultiValueMap();
    
            map.add("token_type_hint", "refresh_token");
            map.add("token", refreshToken);
            map.add("client_id", clientId);
            map.add("client_secret", clientSecret);
    
            WebClient revokeTokenWebClient = WebClient.builder()
                    .baseUrl(revocationEndpoint).build();
    
            revokeTokenWebClient
                    .post()
                    .body(BodyInserters.fromMultipartData(map))
                    .retrieve()
                    .bodyToMono(String.class)
                    .block();
    
            return;
        }
    }
    

    After, in my WebSecurityConfigurerAdapter in the configure method I have setted up the following:

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
          .authorizeRequests()
          .antMatchers("/img/**", "/favicon.ico", "/logged-out", "/oauth/logout")
          .permitAll()
          .anyRequest()
          .fullyAuthenticated()
          .and()
          .logout()
          .addLogoutHandler(logoutHandler)
          .logoutSuccessHandler(oidcLogoutSuccessHandler());
    }
    

    Doing this, I have achieved to automatically revoke the refresh_token when logging out of my application.