Spring Security OAuth2 - Revoca semplice del token (utilizzando lo stack legacy OAuth di Spring Security)

1. Panoramica

In questo breve tutorial, illustreremo come possiamo revocare i token concessi da un server di autorizzazione OAuth implementato con Spring Security .

Quando un utente si disconnette, il suo token non viene immediatamente rimosso dall'archivio dei token; invece, rimane valido finché non scade da solo.

E così, la revoca di un token significherà rimuovere quel token dal token store. Tratteremo l'implementazione del token standard nel framework, non i token JWT.

Nota : questo articolo utilizza il progetto legacy Spring OAuth.

2. TokenStore

Innanzitutto, configuriamo il token store; useremo un JdbcTokenStore , insieme all'origine dati di accompagnamento:

@Bean public TokenStore tokenStore() { return new JdbcTokenStore(dataSource()); } @Bean public DataSource dataSource() { DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName")); dataSource.setUrl(env.getProperty("jdbc.url")); dataSource.setUsername(env.getProperty("jdbc.user")); dataSource.setPassword(env.getProperty("jdbc.pass")); return dataSource; }

3. Il bean DefaultTokenServices

La classe che gestisce tutti i token è DefaultTokenServices - e deve essere definita come un bean nella nostra configurazione:

@Bean @Primary public DefaultTokenServices tokenServices() { DefaultTokenServices defaultTokenServices = new DefaultTokenServices(); defaultTokenServices.setTokenStore(tokenStore()); defaultTokenServices.setSupportRefreshToken(true); return defaultTokenServices; }

4. Visualizzazione dell'elenco dei token

Per scopi di amministrazione, configuriamo anche un modo per visualizzare i token attualmente validi.

Accederemo a TokenStore in un controller e recupereremo i token attualmente archiviati per un ID client specificato:

@Resource(name="tokenStore") TokenStore tokenStore; @RequestMapping(method = RequestMethod.GET, value = "/tokens") @ResponseBody public List getTokens() { List tokenValues = new ArrayList(); Collection tokens = tokenStore.findTokensByClientId("sampleClientId"); if (tokens!=null){ for (OAuth2AccessToken token:tokens){ tokenValues.add(token.getValue()); } } return tokenValues; }

5. Revoca di un token di accesso

Per invalidare un token, utilizzeremo l' API revokeToken () dall'interfaccia ConsumerTokenServices :

@Resource(name="tokenServices") ConsumerTokenServices tokenServices; @RequestMapping(method = RequestMethod.POST, value = "/tokens/revoke/{tokenId:.*}") @ResponseBody public String revokeToken(@PathVariable String tokenId) { tokenServices.revokeToken(tokenId); return tokenId; }

Ovviamente questa è un'operazione molto delicata, quindi dovremmo usarla solo internamente o dovremmo fare molta attenzione a esporla con la sicurezza adeguata.

6. Il front-end

Per il front-end del nostro esempio, visualizzeremo l'elenco dei token validi, il token attualmente utilizzato dall'utente che ha effettuato la richiesta di revoca e un campo in cui l'utente può inserire il token che desidera revocare:

$scope.revokeToken = $resource("//localhost:8082/spring-security-oauth-resource/tokens/revoke/:tokenId", {tokenId:'@tokenId'}); $scope.tokens = $resource("//localhost:8082/spring-security-oauth-resource/tokens"); $scope.getTokens = function(){ $scope.tokenList = $scope.tokens.query(); } $scope.revokeAccessToken = function(){ if ($scope.tokenToRevoke && $scope.tokenToRevoke.length !=0){ $scope.revokeToken.save({tokenId:$scope.tokenToRevoke}); $rootScope.message="Token:"+$scope.tokenToRevoke+" was revoked!"; $scope.tokenToRevoke=""; } }

Se un utente tenta di utilizzare nuovamente un token revocato, riceverà un errore di "token non valido" con codice di stato 401.

7. Revoca del token di aggiornamento

Il token di aggiornamento può essere utilizzato per ottenere un nuovo token di accesso. Ogni volta che un token di accesso viene revocato, il token di aggiornamento ricevuto con esso viene invalidato.

Se vogliamo invalidare anche il token di aggiornamento stesso, possiamo utilizzare il metodo removeRefreshToken () della classe JdbcTokenStore , che rimuoverà il token di aggiornamento dallo store:

@RequestMapping(method = RequestMethod.POST, value = "/tokens/revokeRefreshToken/{tokenId:.*}") @ResponseBody public String revokeRefreshToken(@PathVariable String tokenId) { if (tokenStore instanceof JdbcTokenStore){ ((JdbcTokenStore) tokenStore).removeRefreshToken(tokenId); } return tokenId; }

Per verificare che il token di aggiornamento non sia più valido dopo essere stato revocato, scriveremo il seguente test, in cui otteniamo un token di accesso, lo aggiorniamo, quindi rimuoviamo il token di aggiornamento e tentiamo di aggiornarlo di nuovo.

Vedremo che dopo la revoca, riceveremo l'errore di risposta: "token di aggiornamento non valido":

public class TokenRevocationLiveTest { private String refreshToken; private String obtainAccessToken(String clientId, String username, String password) { Map params = new HashMap(); params.put("grant_type", "password"); params.put("client_id", clientId); params.put("username", username); params.put("password", password); Response response = RestAssured.given().auth(). preemptive().basic(clientId,"secret").and().with().params(params). when().post("//localhost:8081/spring-security-oauth-server/oauth/token"); refreshToken = response.jsonPath().getString("refresh_token"); return response.jsonPath().getString("access_token"); } private String obtainRefreshToken(String clientId) { Map params = new HashMap(); params.put("grant_type", "refresh_token"); params.put("client_id", clientId); params.put("refresh_token", refreshToken); Response response = RestAssured.given().auth() .preemptive().basic(clientId,"secret").and().with().params(params) .when().post("//localhost:8081/spring-security-oauth-server/oauth/token"); return response.jsonPath().getString("access_token"); } private void authorizeClient(String clientId) { Map params = new HashMap(); params.put("response_type", "code"); params.put("client_id", clientId); params.put("scope", "read,write"); Response response = RestAssured.given().auth().preemptive() .basic(clientId,"secret").and().with().params(params). when().post("//localhost:8081/spring-security-oauth-server/oauth/authorize"); } @Test public void givenUser_whenRevokeRefreshToken_thenRefreshTokenInvalidError() { String accessToken1 = obtainAccessToken("fooClientIdPassword", "john", "123"); String accessToken2 = obtainAccessToken("fooClientIdPassword", "tom", "111"); authorizeClient("fooClientIdPassword"); String accessToken3 = obtainRefreshToken("fooClientIdPassword"); authorizeClient("fooClientIdPassword"); Response refreshTokenResponse = RestAssured.given(). header("Authorization", "Bearer " + accessToken3) .get("//localhost:8082/spring-security-oauth-resource/tokens"); assertEquals(200, refreshTokenResponse.getStatusCode()); Response revokeRefreshTokenResponse = RestAssured.given() .header("Authorization", "Bearer " + accessToken1) .post("//localhost:8082/spring-security-oauth-resource/tokens/revokeRefreshToken/"+refreshToken); assertEquals(200, revokeRefreshTokenResponse.getStatusCode()); String accessToken4 = obtainRefreshToken("fooClientIdPassword"); authorizeClient("fooClientIdPassword"); Response refreshTokenResponse2 = RestAssured.given() .header("Authorization", "Bearer " + accessToken4) .get("//localhost:8082/spring-security-oauth-resource/tokens"); assertEquals(401, refreshTokenResponse2.getStatusCode()); } }

8. Conclusione

In questo tutorial, abbiamo dimostrato come revocare un token di accesso OAuth e un token di aggiornamento OAuth.

L'implementazione di questo tutorial può essere trovata nel progetto GitHub.