Spring Security OAuth2 - Pencabutan Token Sederhana (menggunakan tumpukan lama OAuth Keamanan Spring)

1. Ikhtisar

Dalam tutorial singkat ini, kami akan mengilustrasikan bagaimana kami dapat mencabut token yang diberikan oleh Server Otorisasi OAuth yang diimplementasikan dengan Spring Security .

Saat pengguna keluar, token mereka tidak segera dihapus dari penyimpanan token; sebaliknya, ini tetap valid hingga kedaluwarsa dengan sendirinya.

Jadi, pencabutan token berarti menghapus token itu dari penyimpanan token. Kami akan membahas implementasi token standar dalam kerangka kerja, bukan token JWT.

Catatan : artikel ini menggunakan proyek lama Spring OAuth.

2. TokenStore

Pertama, mari kita siapkan penyimpanan token; kami akan menggunakan JdbcTokenStore , bersama dengan sumber data yang menyertainya:

@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. Kacang DefaultTokenServices

Kelas yang menangani semua token adalah DefaultTokenServices - dan harus didefinisikan sebagai kacang dalam konfigurasi kita:

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

4. Menampilkan Daftar Token

Untuk tujuan admin, mari kita juga menyiapkan cara untuk melihat token yang saat ini valid.

Kami akan mengakses TokenStore di pengontrol, dan mengambil token yang saat ini disimpan untuk id klien yang ditentukan:

@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. Mencabut Token Akses

Untuk membuat token tidak valid, kami akan menggunakan revokeToken () API dari antarmuka 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; }

Tentu saja ini adalah operasi yang sangat sensitif jadi kita harus menggunakannya hanya secara internal, atau kita harus berhati-hati untuk mengeksposnya dengan keamanan yang tepat.

6. Bagian Depan

Untuk front-end contoh kami, kami akan menampilkan daftar token yang valid, token yang saat ini digunakan oleh pengguna yang masuk membuat permintaan pencabutan, dan bidang tempat pengguna dapat memasukkan token yang ingin mereka cabut:

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

Jika pengguna mencoba menggunakan token yang dicabut lagi, mereka akan menerima kesalahan 'token tidak valid' dengan kode status 401.

7. Mencabut Token Refresh

Token penyegaran dapat digunakan untuk mendapatkan token akses baru. Setiap kali token akses dicabut, token penyegaran yang diterima bersamanya tidak valid.

Jika kita juga ingin membatalkan token penyegaran itu sendiri, kita dapat menggunakan metode removeRefreshToken () dari kelas JdbcTokenStore , yang akan menghapus token penyegaran dari toko:

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

Untuk menguji apakah token penyegaran tidak lagi valid setelah dicabut, kami akan menulis pengujian berikut, di mana kami memperoleh token akses, menyegarkannya, lalu menghapus token penyegaran, dan mencoba menyegarkannya lagi.

Kami akan melihat bahwa setelah pencabutan, kami akan menerima kesalahan tanggapan: "token penyegaran tidak valid":

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. Kesimpulan

Dalam tutorial ini, kami telah mendemonstrasikan cara mencabut token akses OAuth dan token penyegaran OAuth.

Implementasi tutorial ini dapat ditemukan di proyek GitHub.