知識庫 / Spring / Spring Security RSS 訂閱

OAuth安全應用程序註銷

Spring Security
HongKong
6
02:36 PM · Dec 06 ,2025

1. 概述

本快速教程將演示如何為 OAuth Spring Security 應用程序添加註銷功能。

我們將看到幾種實現方法。首先,我們將從 OAuth 應用程序中註銷 Keycloak 用户,正如在“使用 OAuth2 創建 REST API”中所述;然後,使用我們之前看到的 Zuul 代理。

我們將使用 Spring Security 5 中的 OAuth 棧。 如果您想使用 Spring Security OAuth 遺留棧,請參閲“在 OAuth 安全應用程序中註銷”(使用遺留棧)這篇以前的文章。

2. 使用前端應用程序註銷

由於訪問令牌由授權服務器管理,因此需要在該級別中失效它們。 具體步驟將根據您使用的授權服務器而略有不同。

在我們的示例中,根據 Keycloak 文檔,從瀏覽器應用程序直接註銷時,我們可以將瀏覽器重定向到 http://auth-server/auth/realms/{realm-name}/protocol/openid-connect/logout?redirect_uri=encodedRedirectUri

除了發送重定向 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 令牌,我們應該在 scope 參數中包含 <openid>

現在讓我們觀察登出過程的實際操作。

我們將修改我們 App Service 中的函數 <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;
}

除了重定向之外,我們還需要丟棄來自授權服務器獲得的 Access 和 ID Tokens

因此,在上述代碼中,我們首先刪除了這些 tokens,然後將瀏覽器重定向到 Keycloak 的 logout API。

值得注意的是,我們傳遞了重定向 URI 為 http://localhost:8089/ – 我們在整個應用程序中使用的 URI – 這樣在註銷後,我們就會回到登陸頁面。

當前會話中 Access、ID 和 Refresh Tokens 的刪除在授權服務器端執行。在本例中,我們的瀏覽器應用程序根本沒有保存 Refresh Token。

3. 使用 Zuul 代理註銷

在之前的關於處理刷新令牌的文章中,我們已經配置了應用程序能夠刷新訪問令牌,使用刷新令牌。該實現利用了帶有自定義過濾器的 Zuul 代理。

在此,我們將演示如何向上述應用程序添加註銷功能。

本次,我們將使用另一個 Keycloak API 來註銷用户。我們將通過調用 POST 方法,在 logout 端點上註銷會話,通過非瀏覽器調用,而不是我們在上一部分中使用的 URL 重定向

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 映射到主路由

為了訪問 HTTP-only refreshToken Cookie,我們添加了一個子路由,而不是主路由。該 Cookie 的路徑設置為非常有限的路徑 /auth/refresh (及其子路徑)。 我們將在下一部分中看到為什麼我們需要該 Cookie。

3.2. 向授權服務器發送 POST 請求:/logout

現在,讓我們增強 <em>CustomPreZuulFilter</em> 的實現,使其攔截 <em>/auth/refresh/revoke</em> URL 並將必要的信息傳遞給授權服務器。

註銷所需的表單參數與刷新令牌請求類似,但缺少 grant_type

@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 餅乾,併發送了所需的 formParams

3.3. 取消刷新令牌

當使用先前所述的 <em>logout</em> 重定向方式撤銷 Access Token 時,相關的 Refresh Token 也會被 Authorization Server 無效化。

但是,在這種情況下,客户端仍然會設置 <em>httpOnly</em> Cookie。由於我們無法通過 JavaScript 刪除它,因此需要在服務器端刪除它。

為此,我們將在 <em>CustomPostZuulFilter</em> 的實現中添加攔截器,以便它在遇到 <em>/auth/refresh/revoke</em> URL 時,刪除refreshToken Cookie

@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_token 餅乾。

讓我們為我們的 Angular 控制器添加一個方法,清除 access_token 餅乾並調用 /auth/refresh/revoke POST 映射:

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 認證的應用中註銷用户,並失效該用户的令牌。

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

發佈 評論

Some HTML is okay.