借助Redis实现Token黑名单机制

1. 概述

如果没有为Token提供主动失效机制。一旦Token被签发,在过期之前将一直有效,存在以下安全隐患:

  1. 无法主动注销:用户注销后,已签发的Token仍然有效
  2. 安全风险:Token泄露后无法立即失效
  3. 缺乏控制:无法对特定Token进行精准控制

本博客通过将已失效的Token存储在Redis中,可以确保Token在被主动注销后无法继续使用,提高了系统的安全性和可控性。

2. 具体方案

2.1 设计思路

通过在Redis中维护一个Token黑名单来实现Token的主动失效功能:

  1. 当用户注销或Token需要主动失效时,将Token加入黑名单
  2. 在验证Token时,先检查是否在黑名单中
  3. 如果在黑名单中,则认为Token已失效

2.2 Redis存储设计

复制代码
Key: token:blacklist:{token}
Value: "blacklisted"
Expire: Token剩余有效时间

2.3 TokenUtil中新增方法

2.3.1 addToBlacklist方法
java 复制代码
/**
 * 将Token加入黑名单,实现主动失效
 *
 * @param token JWT token
 * @return 是否成功加入黑名单
 */
public boolean addToBlacklist(String token) {
    try {
        // 获取Token的过期时间
        Date expirationDate = getExpirationDateFromToken(token);
        if (expirationDate == null) {
            return false;
        }

        // 计算剩余有效时间
        long remainingTime = expirationDate.getTime() - System.currentTimeMillis();
        if (remainingTime <= 0) {
            return false;
        }

        // 将Token加入Redis黑名单,设置过期时间为Token剩余有效时间
        String key = TOKEN_BLACKLIST_PREFIX + token;
        return redisUtil.set(key, "blacklisted", remainingTime / 1000);
    } catch (Exception e) {
        return false;
    }
}
2.3.2 isTokenInBlacklist方法
java 复制代码
/**
 * 检查Token是否在黑名单中
 *
 * @param token JWT token
 * @return 是否在黑名单中
 */
public boolean isTokenInBlacklist(String token) {
    String key = TOKEN_BLACKLIST_PREFIX + token;
    return redisUtil.exists(key);
}
2.3.3 removeFromBlacklist方法
java 复制代码
/**
 * 从黑名单中移除Token
 *
 * @param token JWT token
 * @return 是否成功移除
 */
public boolean removeFromBlacklist(String token) {
    String key = TOKEN_BLACKLIST_PREFIX + token;
    return redisUtil.del(key) > 0;
}
2.3.4 修改validateToken方法
java 复制代码
/**
 * 验证Token是否合法且未过期
 *
 * @param token JWT token
 * * @return 是否有效
 */
public Boolean validateToken(String token) {
    try {
        // 检查Token是否在黑名单中
        if (isTokenInBlacklist(token)) {
            return false;
        }

        JwtParser parser = Jwts.parser().verifyWith(secretKey).build();
        parser.parseSignedClaims(token);
        return true;
    } catch (JwtException e) {
        System.out.println("JWT exception: " + e.getMessage());
    } catch (IllegalArgumentException e) {
        System.out.println("JWT claims string is empty: " + e.getMessage());
    }
    return false;
}

3. 使用示例

3.1 用户注销时将Token加入黑名单

java 复制代码
@PostMapping("/logout")
public ResultBean<String> logout(@RequestHeader("Authorization") String token) {
    if (token != null && token.startsWith("Bearer ")) {
        token = token.substring(7);
      
        // 将Token加入黑名单
        boolean added = tokenUtil.addToBlacklist(token);
        if (added) {
            return ResultBean.success("注销成功");
        }
    }
    return ResultBean.error("注销失败");
}

3.2 在拦截器中验证Token

java 复制代码
@Component
public class TokenInterceptor implements HandlerInterceptor {
  
    @Autowired
    private TokenUtil tokenUtil;
  
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("Authorization");
      
        if (token != null && token.startsWith("Bearer ")) {
            token = token.substring(7);
          
            // 验证Token(会自动检查黑名单)
            if (!tokenUtil.validateToken(token)) {
                response.setStatus(HttpStatus.UNAUTHORIZED.value());
                return false;
            }
        }
      
        return true;
    }
}

3.3 管理员强制用户下线

java 复制代码
@PostMapping("/admin/forceLogout")
public ResultBean<String> forceLogout(@RequestParam String token) {
    // 将Token加入黑名单
    boolean added = tokenUtil.addToBlacklist(token);
    if (added) {
        return ResultBean.success("用户已强制下线");
    }
    return ResultBean.error("操作失败");
}

4. 优化效果

4.1 安全性提升

  • 实现了Token的主动失效功能
  • 可以精准控制特定Token的可用性
  • 有效防止Token泄露后的安全风险

4.2 控制性增强

  • 提供了细粒度的Token控制能力
  • 支持用户主动注销和管理员强制下线
  • 可以根据业务需求灵活控制Token生命周期

4.3 用户体验改善

  • 用户注销后Token立即失效
  • 管理员可以强制用户下线
  • 提高了系统的安全性和可控性

5. 注意事项

  1. Redis性能: 黑名单存储在Redis中,需要注意Redis的性能和容量
  2. 过期时间: 黑名单中的Token会自动过期,过期时间与Token剩余有效时间一致
  3. 异常处理: 需要妥善处理Redis操作可能出现的异常
  4. 并发控制: 在高并发场景下需要注意Redis操作的并发控制
相关推荐
小小小手手啊2 年前
Ip-Limit: 轻量级注解式IP限流组件(一)
限流·白名单·黑名单·ip·限流注解