web-dev-qa-db-ja.com

Spring-boot JWTログアウト

私はこのコード https://github.com/gdongus/spring-boot-oauth-jwt-example を使用し、すべてが完璧に機能しますが、ログアウト機能を実装する方法がわかりません。誰かが私にアドバイスを与えることはできますか?ありがとうございました。

13
Martin Morek

クライアント側のログアウトは簡単で、所有しているトークンを破棄するだけです。サーバー側のログアウト機能を提供するには、アプリケーションは現在認証されているクライアント、つまり既存のトークンを認識する必要があります。トークンベースの認証の「組み込み」の問題は、トークンが発行された場合、トークンが期限切れになるまで有効であり、「リモート無効化」ソリューションがないことです。唯一のチャンスは、信頼できないトークンを使用したリクエストへのアクセスを回避することです。

したがって、公開されたすべてのトークンを token store というコンテナーに覚えておく必要があります。

インメモリまたはデータベース(TokenStore)で動作するJdbcTokenStoreインターフェイスの実装がいくつかあります。簡単な例としては、InMemoryTokenStoreで十分です。

これを使用するには、トークンストアを次のように作成および構成する必要があります。

これをAuthorizationServerConfigurationに追加:

@Bean
public InMemoryTokenStore tokenStore() {
    return new InMemoryTokenStore();
}

そして、それをAuthorizationServerEndpointsConfigurerで使用します。

@Override
public void configure(AuthorizationServerEndpointsConfigurer configurer) throws Exception {
    configurer.authenticationManager(authenticationManager);
    configurer.userDetailsService(userDetailsService);
    configurer.accessTokenConverter(accessTokenConverter());
    configurer.tokenStore(tokenStore());
}

ResourceServerConfigurationにも追加します:

@Autowired
private InMemoryTokenStore inMemoryTokenStore;
...
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
    resources.resourceId("resource").tokenStore(inMemoryTokenStore);
}

これでほぼすべてです。これで、必要に応じてログアウト機能を実装できます。トークンを取得してトークンストアから削除するだけでよい特別なエンドポイントを使用できます。

inMemoryTokenStore.removeAccessToken(accessToken);
inMemoryTokenStore.removeRefreshToken(refreshToken);

リフレッシュトークンも削除することに注意してください。そうしないと(アクセストークンのみが削除された場合)、クライアントはリフレッシュトークンを使用して新しいトークンを取得できます。

これが機能しているかどうかを確認するためのテストに応じたテストケースです。

@Test
public void getUserWithValidAuth() throws Exception {
    final HttpHeaders headers = getHttpHeader(CLIENT_USER, CLIENT_SECRET);
    final HttpEntity<String> request = new HttpEntity<>(headers);

    final String tokenUrl = getOAuthTokenUrl(OAUTH_TOKEN_USERNAME, OAUTH_TOKEN_PASSWORD);
    final ResponseEntity<Object> response = restTemplate.exchange(tokenUrl, HttpMethod.POST, request, Object.class);
    assertTrue("Did not get auth tokens!", response.getStatusCode().is2xxSuccessful());

    final Map result = (Map) response.getBody();
    final String accessTokenAsString = (String) result.get(ACCESS_TOKEN);
    final String refreshTokenAsString = (String) result.get(REFRESH_TOKEN);

    final String resourceUrlWithToken = "http://localhost:" + port + "/users?access_token=" + accessTokenAsString;

    final ResponseEntity<String> userResponse = restTemplate.exchange(resourceUrlWithToken, HttpMethod.GET, null,
            String.class);
    assertTrue("Could not request user data!", userResponse.getStatusCode().is2xxSuccessful());

    final OAuth2AccessToken accessToken = inMemoryTokenStore.readAccessToken(accessTokenAsString);
    final OAuth2RefreshToken refreshToken = inMemoryTokenStore.readRefreshToken(refreshTokenAsString);
    inMemoryTokenStore.removeAccessToken(accessToken);
    inMemoryTokenStore.removeRefreshToken(refreshToken);

    try {
        restTemplate.exchange(resourceUrlWithToken, HttpMethod.GET, null, String.class);
        fail("Should not get here, expected 401 for request with access token!");
    } catch (HttpClientErrorException e) {
        // would not be needed with MockMvc
    }

    final String refreshTokenUrl = REFRESH_TOKEN_URL + refreshTokenAsString;
    try {
        restTemplate.exchange(refreshTokenUrl, HttpMethod.POST, request, Object.class);
        fail("Should not get here, expected 401 for request with refresh token!");
    } catch (HttpClientErrorException e) {
        // would not be needed with MockMvc
    }
}

そして、少なくとも推奨事項である MockMvc の使用は、残りの呼び出しを簡単にテストできる素晴らしいテストフレームワークであり、RestTemplateで作業しながら障害物やボイラープレートコードを取り除くことができます。たぶん、あなたはそれを試してみたいと思います。

9
Kevin Peters

ログアウトが完了するとすぐに、アクセストークンと更新トークンの両方が認証サーバーの基盤となるストレージから削除されるため、リソースサーバーでのアクセストークンの無効化については、有効期限が切れるまで心配するだけです。

これを行うには、Spring Stream/Integrationを介してログアウトが完了したらすぐにauth-serverからイベントを公開し、すべてのトークンオーディエンスインスタンスにLogoutイベントをサブスクライブさせる必要があります。

認証サーバーからこのイベントを発行する独自のLogoutHandlerを追加できます。 Springクラウドストリームの@StreamListnerを使用して、各リソースサーバーでこのイベントをリッスンできます

このログアウトイベントには、削除されたアクセストークンと、期限切れになるまでの残り時間が含まれている必要があります。このイベントのすべての受信者は、これらのアクセストークンをメモリ内のブラックリストに保存し、受信したアクセストークンが既存のブラックリストトークンのいずれかに一致する場合、リソースへのアクセスを拒否する必要があります。アクセストークンの有効期限が切れたら、メモリから削除します。キーを自動的に期限切れにするには、グアバのCacheBuilderのようなものを使用できます

だから、全体的に、私の知る限り、JWTの性質上、アクセストークンの期限切れのすぐに使用できるソリューションはありません。

0
Ashok Koyi