知識庫 / Spring / Spring Security RSS 訂閱

Spring Security OAuth2 – 簡單令牌撤銷(使用 Spring Security OAuth 遺留棧)

Spring Security
HongKong
9
02:44 PM · Dec 06 ,2025

1. 概述

在本快速教程中,我們將演示如何撤銷由 OAuth 授權服務器 實現,並使用 Spring Security 實施的令牌授予的權限。

當用户註銷時,其令牌不會立即從令牌存儲中刪除;相反,它將在自身的有效期內保持有效。

因此,撤銷令牌意味着從令牌存儲中移除該令牌。我們將涵蓋框架中的標準令牌實現,而不是 JWT 令牌。

注意:本文檔使用 Spring OAuth 遺留項目

2. JdbcTokenStore (令牌存儲)

首先,我們設置令牌存儲,將使用 JdbcTokenStore,以及相應的數據庫連接池:

@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. 默認標記服務 Bean

該類負責處理所有標記的是 DefaultTokenServices – 它必須在我們的配置中定義為 Bean:

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

4. 顯示令牌列表

為了管理目的,我們還將設置一種查看當前有效令牌的方法。

我們將訪問 TokenStore 在一個控制器中,並檢索指定客户端 ID 的存儲令牌:

@Resource(name="tokenStore")
TokenStore tokenStore;

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

5. 取消訪問令牌

為了無效化令牌,我們將使用 <em >ConsumerTokenServices</em> 接口中的 <em >revokeToken()</em> API:

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

當然,這是一個非常敏感的操作,因此我們應該僅在內部使用它,或者在確保到位了適當的安全措施的情況下,謹慎地對外暴露它。

6. 前端

對於我們示例的前端,我們將顯示有效令牌的列表、當前由已登錄用户使用的用於撤銷請求的令牌,以及用户可以輸入要撤銷的令牌的字段:

$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="";
    }
}

如果用户嘗試再次使用被撤銷的令牌,將會收到一個“無效令牌”錯誤,狀態碼為 401。

7. 取消刷新令牌

刷新令牌可用於獲取新的訪問令牌。每當訪問令牌被撤銷時,與它一同收到的刷新令牌也會失效。

如果想要同時撤銷刷新令牌本身,可以使用 removeRefreshToken() 方法,該方法位於 JdbcTokenStore 類中,該方法會將刷新令牌從存儲中移除:

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

為了測試撤銷後刷新令牌是否不再有效,我們將編寫以下測試,該測試獲取一個訪問令牌,刷新它,然後刪除刷新令牌,並再次嘗試刷新它。

我們將看到,在撤銷後,我們會收到錯誤響應:“無效的刷新令牌”。

public class TokenRevocationLiveTest {
    private String refreshToken;

    private String obtainAccessToken(String clientId, String username, String password) {
        Map<String, String> params = new HashMap<String, String>();
        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<String, String> params = new HashMap<String, String>();
        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<String, String> params = new HashMap<String, String>();
        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. 結論

在本教程中,我們演示瞭如何撤銷 OAuth 訪問令牌和 OAuth 刷新令牌。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.