OAuth安全應用程序註銷

Spring Security
Remote
0
12:29 AM · Nov 30 ,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 令牌,我們應該在範圍參數中包含 openid

現在讓我們看看註銷過程在行動中的情況。

我們將修改我們的函數 logoutApp Service:

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 令牌。

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

值得注意的是,我們傳遞了重定向 URI 作為 http://localhost:8089/ – 我們在整個應用程序中使用的那個 – 所以我們最終會回到登錄頁上。

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

3. 使用 Zuul 代理註銷

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

在這裏,我們將看到如何向上述添加註銷功能。

這次,我們將使用另一個 Keycloak API 來註銷用户。 我們將調用 POST 方法到 logout 端點以通過非瀏覽器調用註銷會話,而不是在之前部分中使用的 URL 重定向,而不是 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 映射到主路由的 URL

為了訪問 HTTP-only refreshToken 屬性,我們添加了一個子路由而不是一個主路由。 我們需要該 cookie 在下一個部分中看到的原因。

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

現在,讓我們修改 CustomPreZuulFilter 實現以攔截 /auth/refresh/revoke 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. 移除刷新令牌

當使用 logout 重定向來撤銷訪問令牌(如我們在前面所見),與該訪問令牌關聯的刷新令牌也將由授權服務器失效

然而,在這種情況下,HTTP-only 屬性將仍然設置在客户端。 由於我們無法通過 JavaScript 刪除它,因此我們需要從服務器端刪除它。

為此,讓我們添加到 CustomPostZuulFilter 實現中,該實現攔截 /auth/refresh/revoke URL,以便在遇到此 URL 時,它將 刪除 refreshToken 屬性

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