1. 概述
本文将演示如何为一个基于 Spring Security OAuth 旧版栈 的 OAuth 应用添加登出(Logout)功能。
我们所基于的项目,是之前文章中提到的 使用 OAuth2 构建 REST API。该应用使用了 Spring OAuth 的旧版实现。
✅ 注意:本文使用的是 Spring OAuth 旧版项目。如果你正在使用 Spring Security 5 的新栈,应参考我们另一篇文章:在 OAuth 应用中实现登出(新栈)。
2. 使访问令牌失效
在 OAuth 保护的应用中,登出的本质是 让当前用户的 Access Token 失效,使其无法再用于访问受保护资源。
如果使用的是 JdbcTokenStore
,那这个过程就是从数据库中删除该 token 记录。
我们可以通过为 /oauth/token
接口增加一个 DELETE 方法来实现这一功能。但由于该路径已被 Spring OAuth 框架占用(用于 POST 获取 token 和 GET 查询),我们不能直接在普通 @Controller
中添加该映射,否则会引发冲突或匹配错误。
✅ 正确做法是使用 @FrameworkEndpoint
注解,让这个 endpoint 被 FrameworkEndpointHandlerMapping
处理,而不是标准的 RequestMappingHandlerMapping
,从而避免路由冲突。
@FrameworkEndpoint
public class RevokeTokenEndpoint {
@Resource(name = "tokenServices")
ConsumerTokenServices tokenServices;
@RequestMapping(method = RequestMethod.DELETE, value = "/oauth/token")
@ResponseBody
public void revokeToken(HttpServletRequest request) {
String authorization = request.getHeader("Authorization");
if (authorization != null && authorization.contains("Bearer")){
String tokenId = authorization.substring("Bearer".length() + 1);
tokenServices.revokeToken(tokenId);
}
}
}
⚠️ 注意:
- 我们从
Authorization
头中提取 Bearer Token。 tokenServices.revokeToken(tokenId)
会同时清除 Access Token 和关联的 Refresh Token(取决于具体实现)。
3. 清理 Refresh Token Cookie
在之前的文章 处理 Refresh Token 中,我们通过 Zuul 网关的过滤器,将授权服务器返回的 refresh_token
保存到一个 httpOnly
的 cookie 中(名为 refreshToken
),以增强安全性。
虽然调用 revokeToken
会使得 Refresh Token 在服务端失效,但 客户端的 cookie 仍然存在。由于 httpOnly
cookie 无法通过 JavaScript 删除,我们必须在服务端响应时主动清除它。
解决方案:增强 CustomPostZuulFilter
,在拦截到 /oauth/token
的 DELETE 请求时,清除 refreshToken
cookie。
@Component
public class CustomPostZuulFilter extends ZuulFilter {
// ...
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
String requestURI = ctx.getRequest().getRequestURI();
String requestMethod = ctx.getRequest().getMethod();
if (requestURI.contains("oauth/token") && requestMethod.equals("DELETE")) {
Cookie cookie = new Cookie("refreshToken", "");
cookie.setMaxAge(0); // 立即过期
cookie.setPath(ctx.getRequest().getContextPath() + "/oauth/token");
ctx.getResponse().addCookie(cookie);
}
// ...
}
}
✅ 关键点:
- 设置
MaxAge = 0
表示立即删除 cookie。 - 路径(path)必须与原始设置一致,否则无法正确清除。
4. 前端 AngularJS 清除 Access Token
除了服务端使 token 失效,前端也需要清除本地的 access_token
cookie,并触发登出请求。
我们在 AngularJS 控制器中添加 logout
方法:
$scope.logout = function() {
logout($scope.loginData);
};
function logout(params) {
var req = {
method: 'DELETE',
url: "oauth/token"
};
$http(req).then(
function(data) {
$cookies.remove("access_token");
window.location.href = "login";
},
function() {
console.log("登出请求失败");
}
);
}
前端登出按钮绑定该方法:
<a class="btn btn-info" href="#" ng-click="logout()">登出</a>
✅ 流程说明:
- 发起 DELETE
/oauth/token
请求,服务端清除 token。 - 成功后,前端删除
access_token
cookie。 - 跳转至登录页。
⚠️ 踩坑提醒:如果 $cookies.remove
不生效,检查 cookie 的 path 和 domain 是否匹配。AngularJS 默认只操作当前 path 的 cookie。
5. 总结
本文简单粗暴地实现了 OAuth 应用的登出功能,核心步骤如下:
- ✅ 使用
@FrameworkEndpoint
添加 DELETE/oauth/token
接口,使 Access Token 失效 - ✅ 在 Zuul 过滤器中清除
httpOnly
的refreshToken
cookie - ✅ 前端 AngularJS 发起登出请求并清除
access_token
cookie
虽然 Spring Security OAuth 旧版已进入维护模式,但在遗留系统中仍广泛使用。理解其 token 管理机制,对维护和升级都至关重要。
完整示例代码见 GitHub:https://github.com/Baeldung/spring-security-oauth/tree/master/oauth-legacy