Spring Security OAuth2 - Pembatalan Token Mudah (menggunakan tumpukan legasi Spring Security OAuth)

1. Gambaran keseluruhan

Dalam tutorial ringkas ini, kami akan menggambarkan bagaimana kami boleh mencabut token yang diberikan oleh Pelayan Pengesahan OAuth yang dilaksanakan dengan Spring Security .

Apabila pengguna log keluar, token mereka tidak dikeluarkan langsung dari kedai token; sebaliknya, ia tetap sah sehingga habis masa berlakunya.

Oleh itu, pembatalan token bermaksud membuang token itu dari kedai token. Kami akan merangkumi pelaksanaan token standard dalam rangka kerja, bukan token JWT.

Catatan : artikel ini menggunakan projek legasi Spring OAuth.

2. Kedai Token

Pertama, mari kita menubuhkan kedai 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 mengendalikan semua token adalah DefaultTokenServices - dan mesti ditakrifkan sebagai kacang dalam konfigurasi kami:

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

4. Memaparkan Senarai Token

Untuk tujuan pentadbir, mari kita tentukan cara untuk melihat token yang masih sah.

Kami akan mengakses TokenStore dalam pengawal, dan mengambil token yang sedang disimpan untuk id pelanggan 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. Membatalkan Token Akses

Untuk membatalkan token, kami akan menggunakan API revokeToken () dari antara muka 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; }

Sudah tentu ini adalah operasi yang sangat sensitif, jadi kita hanya harus menggunakannya secara dalaman, atau kita harus berhati-hati untuk memaparkannya dengan keamanan yang sesuai.

6. Bahagian Depan

Untuk bahagian depan contoh kami, kami akan memaparkan senarai token yang sah, token yang sedang digunakan oleh pengguna yang log masuk membuat permintaan pembatalan, dan medan di mana pengguna dapat memasukkan token yang ingin dibatalkan:

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

Sekiranya pengguna cuba menggunakan token yang dicabut lagi, mereka akan menerima ralat 'token tidak sah' dengan kod status 401.

7. Membatalkan Token Segarkan

Token penyegaran boleh digunakan untuk mendapatkan token akses baru. Setiap kali token akses dibatalkan, token penyegaran yang diterima dengannya tidak sah.

Sekiranya kita mahu membatalkan token penyegaran itu sendiri, kita boleh menggunakan kaedah removeRefreshToken () kelas JdbcTokenStore , yang akan mengeluarkan token penyegaran dari kedai:

@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 bahawa token penyegaran tidak lagi berlaku setelah dicabut, kami akan menulis ujian berikut, di mana kami memperoleh token akses, menyegarkannya, kemudian mengeluarkan token penyegaran, dan cuba menyegarkannya semula.

Kami akan melihat bahawa setelah pembatalan, kami akan menerima ralat tindak balas: "token penyegaran tidak sah":

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

Dalam tutorial ini, kami telah menunjukkan cara membatalkan token akses OAuth dan token penyegaran Oauth.

Pelaksanaan tutorial ini boleh didapati dalam projek GitHub.