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.
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.
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.
Spring security provides following 2 options: Perform the POST logout (this is default and recommended.) Perform the GET logout by disabling CSRF feature.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With