用户删除后如何拦截其仍有效的JWT Token访问请求?
解决方案
JWT本身是无状态的,默认校验逻辑只会验证签名合法性、有效期,不会关联校验存储层的用户状态,所以需要在现有过滤器链中增加用户存在性校验环节,具体实现方案如下:
1. 调整过滤器执行顺序
- 自定义的用户校验过滤器必须排在 JWT解析校验过滤器之后、业务接口权限校验过滤器之前
- 不需要重复解析JWT,直接读取上游JWT过滤器解析完成后存入请求上下文的用户ID(通常存储在JWT payload的
userId字段中)
2. 核心校验逻辑实现
- 拿到用户ID后优先查询已删除/注销用户黑名单,黑名单推荐用Redis存储,过期时间设置为JWT的最大有效时长,避免存储无用数据
- 黑名单未命中的情况下调用用户服务查询数据库,判断对应用户实体是否存在
- 若用户在黑名单中或数据库无对应记录,直接返回401未授权响应,终止过滤器链向下执行,响应提示可设置为
无效凭证,用户不存在 - 首次校验到已删除用户后,同步将用户ID写入Redis黑名单,后续请求直接命中缓存拦截,降低数据库压力
3. 高并发场景优化
如果系统访问量较大,可通过以下方案降低数据库查询压力:
- 在JWT payload中新增
userVersion字段,用户信息变更、删除时该字段自增 - 缓存正常状态用户的ID和对应
userVersion,校验时对比JWT携带的版本和缓存版本是否一致,不一致直接拦截 - 接口请求频率较高的场景,可给正常用户ID加短期缓存(比如5分钟),有效期内重复请求不需要查库
代码示例(SpringBoot环境)
@Component public class InvalidUserTokenFilter extends OncePerRequestFilter { @Autowired private UserRepository userRepository; @Autowired private RedisTemplate<String, String> redisTemplate; // 已删除用户黑名单Redis前缀 private static final String USER_BLACKLIST_KEY = "user:blacklist:"; // JWT最大有效期,这里设置为7天 private static final long JWT_MAX_EXPIRE_DAYS = 7; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 读取JWT过滤器解析后存入上下文的用户ID Long loginUserId = (Long) request.getAttribute("loginUserId"); if (loginUserId != null) { // 优先校验黑名单 Boolean inBlacklist = redisTemplate.hasKey(USER_BLACKLIST_KEY + loginUserId); if (Boolean.TRUE.equals(inBlacklist)) { response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.setContentType("application/json;charset=utf-8"); response.getWriter().write("{\"code\":401,\"msg\":\"无效凭证,用户已被注销\"}"); return; } // 黑名单未命中查库校验用户是否存在 boolean userExists = userRepository.existsById(loginUserId); if (!userExists) { // 写入黑名单避免后续重复查库 redisTemplate.opsForValue().set(USER_BLACKLIST_KEY + loginUserId, "1", JWT_MAX_EXPIRE_DAYS, TimeUnit.DAYS); response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.setContentType("application/json;charset=utf-8"); response.getWriter().write("{\"code\":401,\"msg\":\"无效凭证,用户不存在\"}"); return; } } // 校验通过执行后续过滤器 filterChain.doFilter(request, response); } // 配置过滤器执行顺序,JWT过滤器order为1,权限校验过滤器order为3 @Bean public FilterRegistrationBean<InvalidUserTokenFilter> registerFilter() { FilterRegistrationBean<InvalidUserTokenFilter> registration = new FilterRegistrationBean<>(); registration.setFilter(this); registration.addUrlPatterns("/*"); registration.setOrder(2); return registration; } }
注意事项
- 分布式系统场景下,黑名单必须存储在所有服务节点可访问的公共缓存中间件中
- 不要为了性能完全跳过数据库校验,缓存仅作为降级优化手段,首次拦截必须校验数据库确保逻辑准确
- 过滤器不要拦截登录、注册等不需要携带Token的公开接口,可在过滤器中增加白名单路径判断
内容的提问来源于stack exchange,提问作者Ulaş MÜEZZİNOĞLU
相关产品推荐
相关产品推荐

