You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

用户删除后如何拦截其仍有效的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 03:54:01