JWT身份验证:如何实现登出?
JWT身份验证:如何实现登出?
我为我的Spring Boot应用程序实现了JWT身份验证。总体而言,它的工作方式如下:\n
- \n
- 客户端将用户名和密码发送到登录端点。
- 服务器检查提供的凭据是否有效。
- 如果无效,将返回一个错误。
- 如果有效,将返回一个令牌,该令牌实际上包含了
- 客户端在以后的每个请求中都会发送该令牌。
\n
\n
\n
\n
\n
\n问题是,我们应该如何实现登出功能?\n
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Date; class TokenAuthenticationService { static final long EXPIRATIONTIME = 864_000_000; // 10天 static final String SECRET = "ThisIsASecret"; static final String TOKEN_PREFIX = "Bearer"; static final String HEADER_STRING = "Authorization"; static void addAuthentication(HttpServletResponse res, String username) { String JWT = Jwts .builder() .setSubject(username) .setExpiration( new Date(System.currentTimeMillis() + EXPIRATIONTIME)) .signWith(SignatureAlgorithm.HS512, SECRET).compact(); res.addHeader(HEADER_STRING, TOKEN_PREFIX + " " + JWT); } static Authentication getAuthentication(HttpServletRequest request, UserDetailsService customUserDetailsService) { String token = request.getHeader(HEADER_STRING); if (token != null) { // 解析令牌。 Claims claims = Jwts.parser().setSigningKey(SECRET) .parseClaimsJws(token.replace(TOKEN_PREFIX, "")).getBody(); String userName = claims.getSubject(); Date expirationTime = claims.getExpiration(); if (expirationTime.compareTo(new Date()) < 0) { return null; } UserDetails user = customUserDetailsService.loadUserByUsername(userName); return user != null ? new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), user.getAuthorities()) : null; } return null; } }
\naddAuthentication
由JWTLoginFilter
类在登录时用于发送身份验证代码,`getAuthentication由
JWTAuthenticationFilter
`类使用,该类过滤所有请求到端点。\n在这里,最佳实践是什么?
JWT身份验证:如何实现注销?
在使用JWT身份验证时,注销用户的会话是一个常见的需求。然而,由于JWT的无状态性质,它并没有内置的注销机制。因此,我们需要通过其他方式来实现注销。
以下是一些可能的解决方法:
1) 简单地从客户端移除令牌。
2) 创建一个令牌黑名单,将已注销的令牌添加到黑名单中,并在每次验证令牌时检查黑名单。
3) 保持令牌的过期时间较短,并经常更换令牌。
关于如何使JWT无效的更详细的解决方法可以在Invalidating JSON Web Tokens中找到。
下面是一个示例代码,展示了如何实现令牌黑名单的方法(使用Node.js作为示例):
// 令牌黑名单 const tokenBlacklist = []; // 添加令牌到黑名单 function addToBlacklist(token) { tokenBlacklist.push(token); } // 检查令牌是否在黑名单中 function isTokenBlacklisted(token) { return tokenBlacklist.includes(token); } // 验证令牌 function verifyToken(token) { if (isTokenBlacklisted(token)) { throw new Error("Invalid token"); } // 验证令牌的其他逻辑 } // 示例使用 const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"; verifyToken(token); // 将令牌加入黑名单 addToBlacklist(token);
通过上述方法,我们可以实现JWT的注销功能,确保已注销的令牌无法再被使用。
问题的原因:JWT(JSON Web Token)是一种常用的身份验证机制,但是在使用JWT进行认证时,没有提供默认的注销(logout)功能。由于JWT的特性,一旦生成了有效的token,除非过期或被服务器撤销,否则将一直有效。
解决方法:要实现JWT的注销功能,可以采用以下方法之一:
1. 在服务器端维护一个保存有效token的集合,当用户注销时,从集合中移除对应的token。下次认证时,服务器会检查token是否存在于有效token集合中,若不存在则认证失败。
2. 可以向token中添加一个唯一的标识符,比如计数器,每次注销时增加计数器的值。这样,即使token未过期,但是计数器发生变化,服务器也会拒绝该token。
3. 可以使用Spring的认证管理器(Authentication Manager)来实现注销功能,具体实现方式取决于Spring的配置和自定义代码。默认情况下,Spring的认证管理器可能是基于内存的,但是可以通过添加自定义处理程序来持久化到数据库中,以实现更灵活的管理和历史记录功能。
虽然JWT没有提供默认的注销功能,但是可以通过在服务器端维护有效token的集合、向token中添加唯一标识符或使用Spring的认证管理器等方法来实现注销功能。具体的实现方式取决于系统的需求和架构。
JWT身份验证:如何实现注销?
JWT的最佳实践并不确定,这取决于你构建的应用及其需求。
JWT的好处在于它们是无状态的。你不需要查询数据库来验证令牌。这在你希望减少数据库负载时很好,但在你想要使一个未过期的令牌无效时就变得不好。
可能的解决方案:
- 在数据库中存储JWT。你可以检查哪些令牌是有效的,哪些是被撤销的,但这在我看来违背了使用JWT的目的。
- 从客户端删除令牌。这将阻止客户端能够进行身份验证请求,但如果令牌仍然有效且有其他人能够访问它,令牌仍然可以被使用。这让我想到了下一个点。
- 令牌的有效期较短。让令牌快速过期。根据应用程序的不同,可以是几分钟或半个小时。当客户端删除其令牌时,有一个短暂的时间窗口仍然可以使用令牌。从客户端删除令牌和设置较短的令牌有效期不需要对后端进行重大修改。但是,短暂的令牌有效期意味着用户会不断被登出,因为令牌已过期。
- 令牌轮换。也许引入刷新令牌的概念。当用户登录时,提供他们一个JWT和一个刷新令牌。将刷新令牌存储在数据库中。对于经过身份验证的请求,客户端可以使用JWT,但当令牌过期(或即将过期)时,让客户端使用刷新令牌交换新的JWT。这样,你只需要在用户登录或请求新的JWT时访问数据库。当用户注销时,你需要使存储的刷新令牌无效。否则,即使用户已经注销,仍然可以通过监听连接来获取新的JWT。
- 创建JWT黑名单。根据过期时间,当客户端删除其令牌时,令牌可能仍然有效一段时间。如果令牌的生命周期很短,这可能不是一个问题,但如果你仍然希望立即使令牌无效,你可以创建一个令牌黑名单。当后端接收到注销请求时,从请求中获取JWT并将其存储在内存数据库中。对于每个经过身份验证的请求,你需要检查内存数据库以查看令牌是否已被使无效。为了保持搜索空间小,你可以从黑名单中删除已经过期的令牌。
非常好的解决方案列表,适用于任何通用的JWT实现,独立于实际的系统。