Spring Security OAuth2 - Einfaches Widerrufen von Token

Spring Security OAuth2 - Einfacher Token-Widerruf

1. Überblick

In diesem kurzen Tutorial zeigen wir, wie wir Token widerrufen können, die vonOAuth Authorization Server gewährt werden, die mitSpring Security implementiert wurden.

Wenn sich ein Benutzer abmeldet, wird sein Token nicht sofort aus dem Tokenspeicher entfernt, sondern bleibt gültig, bis es von selbst abläuft.

Wenn Sie ein Token widerrufen, wird dieses Token aus dem Tokenspeicher entfernt.

Beachten Sie auch, dass dieser Artikel nur die Standard-Token-Implementierung im Framework behandelt, nicht die JWT-Token.

2. DieTokenStore

Lassen Sie uns zunächst den Token-Speicher einrichten. Wir verwenden einJdbcTokenStore zusammen mit der zugehörigen Datenquelle:

@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. DieDefaultTokenServices Bohne

Die Klasse, die alle Token verarbeitet, istDefaultTokenServices - und muss in unserer Konfiguration als Bean definiert werden:

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

4. Anzeigen der Token-Liste

Richten Sie für Verwaltungszwecke auch eine Möglichkeit zum Anzeigen der aktuell gültigen Token ein.

Wir greifen auf dieTokenStore in einem Controller zu und rufen die aktuell gespeicherten Token für eine angegebene Client-ID ab:

@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. Zugriffstoken widerrufen

Um ein Token ungültig zu machen, verwenden wir dierevokeToken()-API über dieConsumerTokenServices-Schnittstelle:

@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;
}

Dies ist natürlich eine sehr heikle Operation, daher sollten wir sie entweder nur intern verwenden oder mit größter Sorgfalt darauf achten, sie mit der richtigen Sicherheit freizulegen.

6. Das Frontend

Für das Front-End unseres Beispiels zeigen wir die Liste der gültigen Token, das Token, das derzeit von dem angemeldeten Benutzer verwendet wird, der die Sperranforderung stellt, und ein Feld an, in das der Benutzer das Token eingeben kann, das er widerrufen möchte:

$scope.revokeToken =
  $resource("http://localhost:8082/spring-security-oauth-resource/tokens/revoke/:tokenId",
  {tokenId:'@tokenId'});
$scope.tokens = $resource("http://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="";
    }
}

Wenn ein Benutzer erneut versucht, ein gesperrtes Token zu verwenden, wird ein Fehler mit dem Statuscode 401 angezeigt.

7. Widerrufen des Aktualisierungstokens

Das Aktualisierungstoken kann verwendet werden, um ein neues Zugriffstoken abzurufen. Jedes Mal, wenn ein Zugriffstoken widerrufen wird, wird das mit ihm empfangene Aktualisierungstoken ungültig.

Wenn wir das Aktualisierungstoken selbst ungültig machen möchten, können wir die MethoderemoveRefreshToken() der KlasseJdbcTokenStore verwenden, mit der das Aktualisierungstoken aus dem Speicher entfernt wird:

@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;
}

Um zu testen, ob das Aktualisierungstoken nach dem Widerruf nicht mehr gültig ist, schreiben wir den folgenden Test, in dem wir ein Zugriffstoken erhalten, es aktualisieren, dann das Aktualisierungstoken entfernen und erneut versuchen, es zu aktualisieren.

Wir werden sehen, dass wir nach dem Widerruf den Antwortfehler erhalten: "Ungültiges Aktualisierungstoken":

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("http://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("http://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("http://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("http://localhost:8082/spring-security-oauth-resource/tokens");
        assertEquals(200, refreshTokenResponse.getStatusCode());

        Response revokeRefreshTokenResponse = RestAssured.given()
          .header("Authorization", "Bearer " + accessToken1)
          .post("http://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("http://localhost:8082/spring-security-oauth-resource/tokens");
        assertEquals(401, refreshTokenResponse2.getStatusCode());
    }
}

8. Fazit

In diesem Lernprogramm wurde gezeigt, wie ein OAuth-Zugriffstoken und ein Oauth-Aktualisierungstoken widerrufen werden.

Die Implementierung dieses Tutorials finden Sie inthe GitHub project - dies ist ein Maven-basiertes Projekt, daher sollte es einfach zu importieren und auszuführen sein.