The output on the client side is simple, just cancel your token. In order to provide server-side output functionality, your application must be aware of currently verified clients, in other words, existing tokens. The "built-in" problem with authentication on tokens is that if a token is published, it is valid until its expiration and there is no solution to the "remote invalidity". Your only chance is to avoid access to requests using a token that is no longer trusted.
So, you should remember every published token in the token storage container.
There are some implementations of the TokenStore interface for working in memory or, possibly, with a database ( JdbcTokenStore ). For a simple example, InMemoryTokenStore is enough.
To use it, you need to create and configure the token store as follows.
Add this to your AuthorizationServerConfiguration :
@Bean public InMemoryTokenStore tokenStore() { return new InMemoryTokenStore(); }
And use it in 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 is almost all. Now you can implement your logout functions as you need it, perhaps with a special endpoint where you only need to get the token and delete it from the token store using:
inMemoryTokenStore.removeAccessToken(accessToken); inMemoryTokenStore.removeRefreshToken(refreshToken);
Keep in mind also to remove the update token, otherwise (if only the access token is deleted), the client may receive a new one with the update token.
Here is a test case according to your tests to check if it works:
@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) {
And at least just a recommendation using MockMvc is an amazing test environment that simplifies and you can also get rid of the obstacles and boiler room code when working with RestTemplate. Perhaps you want to try it.