Spring security. How to log out user (revoke oauth2 token)

64,635

Solution 1

Here's my implementation (Spring OAuth2):

@Controller
public class OAuthController {
    @Autowired
    private TokenStore tokenStore;

    @RequestMapping(value = "/oauth/revoke-token", method = RequestMethod.GET)
    @ResponseStatus(HttpStatus.OK)
    public void logout(HttpServletRequest request) {
        String authHeader = request.getHeader("Authorization");
        if (authHeader != null) {
            String tokenValue = authHeader.replace("Bearer", "").trim();
            OAuth2AccessToken accessToken = tokenStore.readAccessToken(tokenValue);
            tokenStore.removeAccessToken(accessToken);
        }
    }
}

For testing:

curl -X GET -H "Authorization: Bearer $TOKEN" http://localhost:8080/backend/oauth/revoke-token

Solution 2

The response by camposer can be improved using the API provided by Spring OAuth. In fact, it's not necessary to access directly to the HTTP headers, but the REST method which removes the access token can be implemented as follows:

@Autowired
private AuthorizationServerTokenServices authorizationServerTokenServices;

@Autowired
private ConsumerTokenServices consumerTokenServices;

@RequestMapping("/uaa/logout")
public void logout(Principal principal, HttpServletRequest request, HttpServletResponse response) throws IOException {

    OAuth2Authentication oAuth2Authentication = (OAuth2Authentication) principal;
    OAuth2AccessToken accessToken = authorizationServerTokenServices.getAccessToken(oAuth2Authentication);
    consumerTokenServices.revokeToken(accessToken.getValue());

    String redirectUrl = getLocalContextPathUrl(request)+"/logout?myRedirect="+getRefererUrl(request);
    log.debug("Redirect URL: {}",redirectUrl);

    response.sendRedirect(redirectUrl);

    return;
}

I also added a redirect to the endpoint of Spring Security logout filter, so the session is invalidated and the client must provide credentials again in order to access to the /oauth/authorize endpoint.

Solution 3

It depends on type of oauth2 'grant type' that you're using.

The most common if your have used spring's @EnableOAuth2Sso in your client app is 'Authorization Code'. In this case Spring security redirects login request to the 'Authorization Server' and creates a session in your client app with the data received from 'Authorization Server'.

You can easy destroy your session at the client app calling /logout endpoint, but then client app sends user again to 'authorization server' and returns logged again.

I propose to create a mechanism to intercept logout request at client app and from this server code, call "authorization server" to invalidate the token.

The first change that we need is create one endpoint at the authorization server, using the code proposed by Claudio Tasso, to invalidate the user's access_token.

@Controller
@Slf4j
public class InvalidateTokenController {


    @Autowired
    private ConsumerTokenServices consumerTokenServices;


    @RequestMapping(value="/invalidateToken", method= RequestMethod.POST)
    @ResponseBody
    public Map<String, String> logout(@RequestParam(name = "access_token") String accessToken) {
        LOGGER.debug("Invalidating token {}", accessToken);
        consumerTokenServices.revokeToken(accessToken);
        Map<String, String> ret = new HashMap<>();
        ret.put("access_token", accessToken);
        return ret;
    }
}

Then at the client app, create a LogoutHandler:

@Slf4j
@Component
@Qualifier("mySsoLogoutHandler")
public class MySsoLogoutHandler implements LogoutHandler {

    @Value("${my.oauth.server.schema}://${my.oauth.server.host}:${my.oauth.server.port}/oauth2AuthorizationServer/invalidateToken")
    String logoutUrl;

    @Override
    public void logout(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) {

        LOGGER.debug("executing MySsoLogoutHandler.logout");
        Object details = authentication.getDetails();
        if (details.getClass().isAssignableFrom(OAuth2AuthenticationDetails.class)) {

            String accessToken = ((OAuth2AuthenticationDetails)details).getTokenValue();
            LOGGER.debug("token: {}",accessToken);

            RestTemplate restTemplate = new RestTemplate();

            MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
            params.add("access_token", accessToken);

            HttpHeaders headers = new HttpHeaders();
            headers.add("Authorization", "bearer " + accessToken);

            HttpEntity<String> request = new HttpEntity(params, headers);

            HttpMessageConverter formHttpMessageConverter = new FormHttpMessageConverter();
            HttpMessageConverter stringHttpMessageConverternew = new StringHttpMessageConverter();
            restTemplate.setMessageConverters(Arrays.asList(new HttpMessageConverter[]{formHttpMessageConverter, stringHttpMessageConverternew}));
            try {
                ResponseEntity<String> response = restTemplate.exchange(logoutUrl, HttpMethod.POST, request, String.class);
            } catch(HttpClientErrorException e) {
                LOGGER.error("HttpClientErrorException invalidating token with SSO authorization server. response.status code: {}, server URL: {}", e.getStatusCode(), logoutUrl);
            }
        }


    }
}

And register it at WebSecurityConfigurerAdapter:

@Autowired
MySsoLogoutHandler mySsoLogoutHandler;

@Override
public void configure(HttpSecurity http) throws Exception {
    // @formatter:off
    http
        .logout()
            .logoutSuccessUrl("/")
            // using this antmatcher allows /logout from GET without csrf as indicated in
            // https://docs.spring.io/spring-security/site/docs/current/reference/html/csrf.html#csrf-logout
            .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
            // this LogoutHandler invalidate user token from SSO
            .addLogoutHandler(mySsoLogoutHandler)
    .and()
            ...
    // @formatter:on
}

One note: If you're using JWT web tokens, you can't invalidate it, because the token is not managed by the authorization server.

Solution 4

Its up to your Token Store Implementation.

If you use JDBC token stroke then you just need to remove it from table... Anyway you must add /logout endpoint manually then call this :

@RequestMapping(value = "/logmeout", method = RequestMethod.GET)
@ResponseBody
public void logmeout(HttpServletRequest request) {
    String token = request.getHeader("bearer ");
    if (token != null && token.startsWith("authorization")) {

        OAuth2AccessToken oAuth2AccessToken = okenStore.readAccessToken(token.split(" ")[1]);

        if (oAuth2AccessToken != null) {
            tokenStore.removeAccessToken(oAuth2AccessToken);
        }
}

Solution 5

Programmatically you can log out this way:

public void logout(HttpServletRequest request, HttpServletResponse response) {
    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
      if (auth != null){    
         new SecurityContextLogoutHandler().logout(request, response, auth);
      }
    SecurityContextHolder.getContext().setAuthentication(null);
}
Share:
64,635
gstackoverflow
Author by

gstackoverflow

Updated on June 12, 2020

Comments

  • gstackoverflow
    gstackoverflow almost 4 years

    When I want to get logout I invoke this code:

    request.getSession().invalidate();
    SecurityContextHolder.getContext().setAuthentication(null);
    

    But after it (in next request using old oauth token) I invoke

    SecurityContextHolder.getContext().getAuthentication();

    and I see my old user there.

    How to fix it?