在OAuth保护的应用程序中注销

2025/04/03

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的Logout端点,这应该携带编码的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

现在让我们看看注销过程的具体过程。

我们将修改应用服务中的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的logout API。

值得注意的是,我们传递的重定向URI是http://localhost:8089/(我们在整个应用程序中使用的URI),因此退出后我们将进入登录页面。

当前会话对应的访问令牌、ID令牌和刷新令牌的删除是在授权服务器端进行的,在这种情况下,我们的浏览器应用程序根本没有保存刷新令牌。

3. 使用Zuul代理注销

在上一篇关于处理刷新令牌的文章中,我们已将应用程序设置为能够使用刷新令牌刷新访问令牌,此实现利用了带有自定义过滤器的Zuul代理。

在这里我们将看到如何向上面添加注销功能。

这次,我们将使用另一个Keycloak API来注销用户。我们将在logout端点上调用POST以通过非浏览器调用注销会话,而不是我们在上一节中使用的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的refreshToken Cookie,该Cookie被设置为具有非常有限的路径,如/auth/refresh(及其子路径)。我们将在下一节中看到为什么我们需要该Cookie。

3.2 POST到授权服务器的/logout

现在让我们增强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 Cookie并发送了所需的formParams。

3.3 删除刷新令牌

正如我们之前看到的,当使用logout重定向撤销访问令牌时,与其关联的刷新令牌也会被授权服务器失效

但是,在这种情况下,httpOnly Cookie仍会保留在客户端上。鉴于我们无法通过JavaScript将其删除,我们需要从服务器端将其删除。

为此,让我们添加到拦截/auth/refresh/revoke URL的CustomPostZuulFilter实现中,以便在遇到此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 Cookie。

让我们向Angular控制器添加一个方法,清除access_token Cookie并调用/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')
        );
}

单击“logout”按钮时将调用此函数:

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

4. 总结

在这个快速但深入的教程中,我们展示了如何从OAuth安全应用程序中注销用户并使该用户的令牌无效。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章