1. 概要

このクイックチュートリアルでは、 OAuthSpringSecurityアプリケーションにログアウト機能を追加する方法を紹介します。

これを行うには、いくつかの方法があります。 最初に、 OAuth2を使用したRESTAPIの作成で説明されているように、OAuthアプリケーションからKeycloakユーザーをログアウトする方法を説明します。次に、前に見たZuulプロキシを使用します

SpringSecurity5でOAuthスタックを使用します。SpringSecurity OAuthレガシースタックを使用する場合は、次の前の記事を参照してください: OAuthセキュアアプリケーションでのログアウト(レガシースタック)

2. フロントエンドアプリケーションを使用したログアウト

アクセストークンは認証サーバーによって管理されているため、このレベルで無効にする必要があります。これを行うための正確な手順は、使用している認証サーバーによって少し異なります。

この例では、Keycloak ドキュメントに従って、ブラウザアプリケーションから直接ログアウトするために、ブラウザを http:// auth-server / auth / realms/{realm-nameにリダイレクトできます。 } / protocol / openid-connect / logout?redirect_uri =encodeRedirectUri

リダイレクトURIの送信に加えて、id_token_hintをKeycloakのログアウトエンドポイントに渡す必要もあります。 これには、エンコードされたid_token値が含まれている必要があります。

access_token をどのように保存したかを思い出してみましょう。同様に、id_tokenも保存します。

saveToken(token) {
  var expireDate = new Date().getTime() + (1000 * token.expires_in);
  Cookie.set("access_token", token.access_token, expireDate);
  Cookie.set("id_token", token.id_token, expireDate);
  this._router.navigate(['/']);
}

重要なことに、承認サーバーの応答ペイロードでIDトークンを取得するには、スコープパラメーターにopenidを含める必要があります。

次に、ログアウトプロセスの動作を見てみましょう。

AppServiceの関数logoutを変更します。

logout() {
  let token = Cookie.get('id_token');
  Cookie.delete('access_token');
  Cookie.delete('id_token');
  let logoutURL = "http://localhost:8083/auth/realms/baeldung/protocol/openid-connect/logout?
    id_token_hint=" + token + "&post_logout_redirect_uri=" + this.redirectUri;

  window.location.href = logoutURL;
}

リダイレクトとは別に、認証サーバーから取得したアクセストークンとIDトークンも破棄する必要があります。

したがって、上記のコードでは、最初にトークンを削除してから、ブラウザをKeycloakの logoutAPIにリダイレクトしました。

特に、リダイレクトURIを http:// localhost:8089 / (アプリケーション全体で使用しているもの)として渡したため、ログアウトするとランディングページが表示されます。

現在のセッションに対応するアクセス、ID、および更新トークンの削除は、認証サーバーの側で実行されます。 この場合、ブラウザアプリケーションは更新トークンをまったく保存していませんでした。

3. Zuulプロキシを使用したログアウト

更新トークンの処理に関する前回の記事では、更新トークンを使用してアクセストークンを更新できるようにアプリケーションを設定しました。 この実装では、カスタムフィルターを備えたZuulプロキシを利用します。

ここでは、上記にログアウト機能を追加する方法を説明します。

今回は、別のKeycloakAPIを使用してユーザーをログアウトします。 前のセクションで使用したURLリダイレクトの代わりに、ブラウザ以外の呼び出しを介してセッションをログアウトするために、ログアウトエンドポイントでPOSTを呼び出します。

3.1. ログアウトのルートを定義する

まず、application.ymlのプロキシに別のルートを追加しましょう。

zuul:
  routes:
    //...
    auth/refresh/revoke:
      path: /auth/refresh/revoke/**
      sensitiveHeaders:
      url: http://localhost:8083/auth/realms/baeldung/protocol/openid-connect/logout
    
    //auth/refresh route

実際、既存の auth /refreshにサブルートを追加しました。 メインルートの前にサブルートを追加することが重要です。そうしないと、Zuulは常にメインルートのURLをマップします

/authとして非常に制限されたパスを持つように設定されたHTTPのみのrefreshToken Cookie にアクセスするために、メインルートの代わりにサブルートを追加しました/ refresh (およびそのサブパス)。 次のセクションで、Cookieが必要な理由を説明します。

3.2. 承認サーバーの/logoutへのPOST

次に、 CustomPreZuulFilter 実装を拡張して、 / auth / refresh / revoke URLをインターセプトし、承認サーバーに渡すために必要な情報を追加しましょう。

ログアウトに必要なフォームパラメータは、grant_type がないことを除いて、RefreshTokenリクエストのフォームパラメータと同様です。

@Component 
public class CustomPostZuulFilter extends ZuulFilter { 
    //... 
    @Override 
    public Object run() { 
        //...
        if (requestURI.contains("auth/refresh/revoke")) {
            String cookieValue = extractCookie(req, "refreshToken");
            String formParams = String.format("client_id=%s&client_secret=%s&refresh_token=%s", 
              CLIENT_ID, CLIENT_SECRET, cookieValue);
            bytes = formParams.getBytes("UTF-8");
        }
        //...
    }
}

ここでは、 refreshToken cookieを抽出し、必要なformParams。で送信しました。

3.3. 更新トークンを削除します

前に見たように、 logout リダイレクトを使用してアクセストークンを取り消すと、それに関連付けられている更新トークンも承認サーバーによって無効になります。

ただし、この場合、 httpOnlyCookieはクライアントに設定されたままになります。 JavaScriptで削除できないため、サーバー側から削除する必要があります。

そのために、 / auth / refresh / revokeURLをインターセプトするCustomPostZuulFilter実装に追加して、このURLに遭遇したときに refreshTokencookieを削除するようにします。

@Component
public class CustomPostZuulFilter extends ZuulFilter {
    //...
    @Override
    public Object run() {
        //...
        String requestMethod = ctx.getRequest().getMethod();
        if (requestURI.contains("auth/refresh/revoke")) {
            Cookie cookie = new Cookie("refreshToken", "");
            cookie.setMaxAge(0);
            ctx.getResponse().addCookie(cookie);
        }
        //...
    }
}

3.4. Angularクライアントからアクセストークンを削除します

更新トークンを取り消すだけでなく、 access_tokencookieもクライアント側から削除する必要があります。

access_token cookieをクリアし、 / auth / refresh / revokePOSTマッピングを呼び出すメソッドをAngularコントローラーに追加しましょう。

logout() {
  let headers = new HttpHeaders({
    'Content-type': 'application/x-www-form-urlencoded; charset=utf-8'});
  
  this._http.post('auth/refresh/revoke', {}, { headers: headers })
    .subscribe(
      data => {
        Cookie.delete('access_token');
        window.location.href = 'http://localhost:8089/';
        },
      err => alert('Could not logout')
    );
}

この関数は、[ログアウト]ボタンをクリックすると呼び出されます。

<a class="btn btn-default pull-right"(click)="logout()" href="#">Logout</a>

4. 結論

この迅速で詳細なチュートリアルでは、 OAuth で保護されたアプリケーションからユーザーをログアウトし、そのユーザーのトークンを無効にする方法を示しました。

例の完全なソースコードは、GitHubにあります。