Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring-boot JWT logout

I use this code https://github.com/gdongus/spring-boot-oauth-jwt-example and everything works perfect, but I don't know how to implement logout functionality. Can somebody give me advice? Thank you.

like image 665
Martin Morek Avatar asked Dec 26 '15 22:12

Martin Morek


People also ask

How do I get rid of JWT tokens on logout?

On the client side, delete the cookie from the browser using javascript. On the server side, set the cookie value to an empty string or something useless (for example "deleted" ), and set the cookie expiration time to a time in the past. On the server side, update the refreshtoken stored in your database.

How do you invalidate a JWT token spring boot?

Change password — Invalidate the tokenAdd the old token into the blacklist sections either in the cache Redis (the best option) or database. So when validating the token process, you should check if the token is valid and not expired first, if it is true, check one more condition if the token is in blocklist or not.

How do I logout of spring boot security?

Spring security provides following 2 options: Perform the POST logout (this is default and recommended.) Perform the GET logout by disabling CSRF feature.

Does JWT token expire on logout?

Yeah, the tokens can be expired. but, you can't do that on demand. In the above example, the iat field here stands for “issued at”. This token is set to expire 5 seconds after it was issued.


1 Answers

The client-side logout is simple, just discard the token you own. To provide a server-side logout functionality your application has to be aware of currently authenticated clients, in other words, existing tokens. The "build-in" problem with the token based authentication is that if a token is published it is valid until it expires and there is no "remote invalidation" solution. Your only chance is to avoid access for requests with a token you don't trust anymore.

So you have to remember every published token in a container called token store.

There are some implementations of the TokenStore interface to work in-memory or maybe with a database (JdbcTokenStore). For a simple example the InMemoryTokenStore is totally sufficient.

To use it, a token store has to be created and configured as follows.

Add this to your AuthorizationServerConfiguration:

@Bean
public InMemoryTokenStore tokenStore() {
    return new InMemoryTokenStore();
}

And use it in the AuthorizationServerEndpointsConfigurer:

@Override
public void configure(AuthorizationServerEndpointsConfigurer configurer) throws Exception {
    configurer.authenticationManager(authenticationManager);
    configurer.userDetailsService(userDetailsService);
    configurer.accessTokenConverter(accessTokenConverter());
    configurer.tokenStore(tokenStore());
}

Add it also to your ResourceServerConfiguration:

@Autowired
private InMemoryTokenStore inMemoryTokenStore;
...
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
    resources.resourceId("resource").tokenStore(inMemoryTokenStore);
}

That's nearly all. Now you can implement your logout functionality like you need it, maybe with a special endpoint where you only have to get the token(s) and remove it from the token store with:

inMemoryTokenStore.removeAccessToken(accessToken);
inMemoryTokenStore.removeRefreshToken(refreshToken);

Be aware to also remove the refresh token, otherwise (if only the access token is removed) the client is able to gain a new one with the refresh token.

Here is a test case according to your tests to verify if it's working:

@Test
public void getUserWithValidAuth() throws Exception {
    final HttpHeaders headers = getHttpHeader(CLIENT_USER, CLIENT_SECRET);
    final HttpEntity<String> request = new HttpEntity<>(headers);

    final String tokenUrl = getOAuthTokenUrl(OAUTH_TOKEN_USERNAME, OAUTH_TOKEN_PASSWORD);
    final ResponseEntity<Object> response = restTemplate.exchange(tokenUrl, HttpMethod.POST, request, Object.class);
    assertTrue("Did not get auth tokens!", response.getStatusCode().is2xxSuccessful());

    final Map result = (Map) response.getBody();
    final String accessTokenAsString = (String) result.get(ACCESS_TOKEN);
    final String refreshTokenAsString = (String) result.get(REFRESH_TOKEN);

    final String resourceUrlWithToken = "http://localhost:" + port + "/users?access_token=" + accessTokenAsString;

    final ResponseEntity<String> userResponse = restTemplate.exchange(resourceUrlWithToken, HttpMethod.GET, null,
            String.class);
    assertTrue("Could not request user data!", userResponse.getStatusCode().is2xxSuccessful());

    final OAuth2AccessToken accessToken = inMemoryTokenStore.readAccessToken(accessTokenAsString);
    final OAuth2RefreshToken refreshToken = inMemoryTokenStore.readRefreshToken(refreshTokenAsString);
    inMemoryTokenStore.removeAccessToken(accessToken);
    inMemoryTokenStore.removeRefreshToken(refreshToken);

    try {
        restTemplate.exchange(resourceUrlWithToken, HttpMethod.GET, null, String.class);
        fail("Should not get here, expected 401 for request with access token!");
    } catch (HttpClientErrorException e) {
        // would not be needed with MockMvc
    }

    final String refreshTokenUrl = REFRESH_TOKEN_URL + refreshTokenAsString;
    try {
        restTemplate.exchange(refreshTokenUrl, HttpMethod.POST, request, Object.class);
        fail("Should not get here, expected 401 for request with refresh token!");
    } catch (HttpClientErrorException e) {
        // would not be needed with MockMvc
    }
}

And at least just a recommendation, using MockMvc is an awesome test framework which makes it easy to test rest calls and you can get rid of the obstacles and boiler-plate code while working with the RestTemplate. Maybe you want to give it a try.

like image 117
Kevin Peters Avatar answered Sep 28 '22 17:09

Kevin Peters