标题: 黑白名单还在用数据库?Redis+布隆过滤器来了!
副标题: 从IP封禁到用户拉黑,打造高性能访问控制
🎬 开篇:一次恶意攻击的防御战
某API平台遭受恶意攻击:
攻击者:用脚本疯狂刷接口
QPS:从100 -> 10000 💀
服务器:CPU 100%
数据库:查询变慢
正常用户:无法访问 😭
紧急处理:
运营:这个IP一直在刷,能不能封掉?
开发:好的(加入黑名单)
数据库:每次请求都查询黑名单表...💀
性能:更慢了!
改用Redis后:
- 黑名单检查:O(1)时间复杂度 ⚡
- 响应时间:从50ms -> 1ms 🚀
- 自动过期:24小时后自动解封
- 攻击者:被完美拦截 ✅
效果:
- 恶意流量:拦截100%
- 正常用户:不受影响
- 服务器:恢复正常
- 成本:降低90%
教训:黑白名单要用高性能缓存!
🤔 什么是黑白名单?
想象一下:
- 黑名单: 恶意IP、违规用户、刷单账号
- 白名单: VIP用户、内部IP、合作伙伴
- 灰名单: 可疑用户、需要验证的行为
黑白名单 = 访问控制 + 风险防控 + 高性能查询!
📚 知识地图
黑白名单系统
├── 🎯 应用场景
│ ├── IP封禁(黑名单)
│ ├── 用户拉黑(黑名单)
│ ├── VIP特权(白名单)
│ ├── 接口限流(黑白名单)
│ └── 内容审核(敏感词)
├── 💾 存储方案
│ ├── Redis Set(推荐)⭐⭐⭐⭐⭐
│ ├── 布隆过滤器(海量数据)⭐⭐⭐⭐⭐
│ ├── MySQL(持久化)⭐⭐⭐
│ └── Guava Cache(本地缓存)⭐⭐⭐
├── ⚡ 核心功能
│ ├── 添加/删除
│ ├── 快速查询
│ ├── 自动过期
│ ├── 批量导入
│ └── 命中统计
└── 📊 高级功能
├── 动态更新
├── 等级分类
├── 临时封禁
└── 白名单优先
⚡ 方案1:Redis Set(推荐)
1. IP黑名单实现
/**
* IP黑名单服务
*/
@Service
@Slf4j
public class IpBlacklistService {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 黑名单Key
*/
private static final String BLACKLIST_KEY = "blacklist:ip";
/**
* 白名单Key
*/
private static final String WHITELIST_KEY = "whitelist:ip";
/**
* ⚡ 添加IP到黑名单
*/
public void addToBlacklist(String ip, Long duration, TimeUnit unit) {
// 添加到黑名单Set
redisTemplate.opsForSet().add(BLACKLIST_KEY, ip);
// 设置过期时间(单独为这个IP设置)
if (duration != null && duration > 0) {
String tempKey = BLACKLIST_KEY + ":temp:" + ip;
redisTemplate.opsForValue().set(tempKey, "1", duration, unit);
}
log.info("IP加入黑名单:ip={}, duration={}{}",
ip, duration, unit);
}
/**
* ⚡ 从黑名单移除IP
*/
public void removeFromBlacklist(String ip) {
redisTemplate.opsForSet().remove(BLACKLIST_KEY, ip);
// 删除临时Key
String tempKey = BLACKLIST_KEY + ":temp:" + ip;
redisTemplate.delete(tempKey);
log.info("IP从黑名单移除:ip={}", ip);
}
/**
* ⚡ 检查IP是否在黑名单(O(1)复杂度)
*/
public boolean isBlacklisted(String ip) {
// 先检查白名单(白名单优先)
if (isWhitelisted(ip)) {
return false;
}
// 检查黑名单
Boolean result = redisTemplate.opsForSet().isMember(BLACKLIST_KEY, ip);
if (result != null && result) {
// 记录命中次数
incrementHitCount(ip, "blacklist");
return true;
}
return false;
}
/**
* ⚡ 添加IP到白名单
*/
public void addToWhitelist(String ip) {
redisTemplate.opsForSet().add(WHITELIST_KEY, ip);
log.info("IP加入白名单:ip={}", ip);
}
/**
* 从白名单移除IP
*/
public void removeFromWhitelist(String ip) {
redisTemplate.opsForSet().remove(WHITELIST_KEY, ip);
log.info("IP从白名单移除:ip={}", ip);
}
/**
* 检查IP是否在白名单
*/
public boolean isWhitelisted(String ip) {
Boolean result = redisTemplate.opsForSet().isMember(WHITELIST_KEY, ip);
return result != null && result;
}
/**
* ⚡ 批量添加黑名单
*/
public void batchAddToBlacklist(List<String> ips) {
if (ips == null || ips.isEmpty()) {
return;
}
redisTemplate.opsForSet().add(BLACKLIST_KEY, ips.toArray(new String[0]));
log.info("批量添加黑名单:count={}", ips.size());
}
/**
* 获取黑名单列表
*/
public Set<String> getBlacklist() {
return redisTemplate.opsForSet().members(BLACKLIST_KEY);
}
/**
* 获取白名单列表
*/
public Set<String> getWhitelist() {
return redisTemplate.opsForSet().members(WHITELIST_KEY);
}
/**
* 黑名单数量
*/
public Long getBlacklistSize() {
return redisTemplate.opsForSet().size(BLACKLIST_KEY);
}
/**
* ⚡ 记录命中次数
*/
private void incrementHitCount(String ip, String type) {
String key = "blacklist:hit:" + type + ":" +
LocalDate.now().toString();
redisTemplate.opsForHash().increment(key, ip, 1);
// 设置过期时间(保留7天)
redisTemplate.expire(key, 7, TimeUnit.DAYS);
}
/**
* 获取IP命中次数
*/
public Long getHitCount(String ip, String type) {
String key = "blacklist:hit:" + type + ":" +
LocalDate.now().toString();
Object count = redisTemplate.opsForHash().get(key, ip);
return count != null ? Long.parseLong(count.toString()) : 0L;
}
}
2. 用户黑名单实现
/**
* 用户黑名单服务
*/
@Service
@Slf4j
public class UserBlacklistService {
@Autowired
private StringRedisTemplate redisTemplate;
private static final String USER_BLACKLIST_KEY = "blacklist:user";
/**
* ⚡ 拉黑用户
*/
public void blockUser(Long userId, String reason, Long duration, TimeUnit unit) {
// 1. 添加到黑名单
redisTemplate.opsForSet().add(USER_BLACKLIST_KEY, String.valueOf(userId));
// 2. 记录拉黑原因和时间
UserBlacklistInfo info = new UserBlacklistInfo();
info.setUserId(userId);
info.setReason(reason);
info.setBlockTime(new Date());
if (duration != null && duration > 0) {
info.setExpireTime(new Date(System.currentTimeMillis() +
unit.toMillis(duration)));
}
String infoKey = "blacklist:user:info:" + userId;
redisTemplate.opsForValue().set(infoKey, JSON.toJSONString(info),
duration != null ? duration : 0,
duration != null ? unit : TimeUnit.SECONDS);
log.info("用户被拉黑:userId={}, reason={}, duration={}{}",
userId, reason, duration, unit);
}
/**
* ⚡ 解除拉黑
*/
public void unblockUser(Long userId) {
redisTemplate.opsForSet().remove(USER_BLACKLIST_KEY, String.valueOf(userId));
String infoKey = "blacklist:user:info:" + userId;
redisTemplate.delete(infoKey);
log.info("用户解除拉黑:userId={}", userId);
}
/**
* ⚡ 检查用户是否被拉黑
*/
public boolean isBlocked(Long userId) {
Boolean result = redisTemplate.opsForSet().isMember(
USER_BLACKLIST_KEY, String.valueOf(userId));
return result != null && result;
}
/**
* 获取拉黑信息
*/
public UserBlacklistInfo getBlacklistInfo(Long userId) {
String infoKey = "blacklist:user:info:" + userId;
String info = redisTemplate.opsForValue().get(infoKey);
if (StringUtils.isNotBlank(info)) {
return JSON.parseObject(info, UserBlacklistInfo.class);
}
return null;
}
}
/**
* 用户黑名单信息
*/
@Data
public class UserBlacklistInfo {
private Long userId;
private String reason;
private Date blockTime;
private Date expireTime;
}
🎯 方案2:布隆过滤器(海量数据)
/**
* 布隆过滤器黑名单(适用于海量IP)
*/
@Service
@Slf4j
public class BloomFilterBlacklistService {
@Autowired
private RedissonClient redissonClient;
private RBloomFilter<String> ipBlacklistFilter;
/**
* 初始化布隆过滤器
*/
@PostConstruct
public void init() {
// ⚡ 创建布隆过滤器
ipBlacklistFilter = redissonClient.getBloomFilter("blacklist:ip:bloom");
// 初始化(预期元素数量:1000万,误判率:0.01%)
ipBlacklistFilter.tryInit(10_000_000L, 0.0001);
log.info("布隆过滤器初始化完成");
}
/**
* ⚡ 添加IP到黑名单
*/
public void addToBlacklist(String ip) {
ipBlacklistFilter.add(ip);
log.info("IP加入布隆过滤器黑名单:ip={}", ip);
}
/**
* ⚡ 检查IP是否可能在黑名单(可能有误判)
*/
public boolean mightContain(String ip) {
return ipBlacklistFilter.contains(ip);
}
/**
* ⚡ 精确检查(布隆过滤器+Redis Set双重验证)
*/
public boolean isBlacklisted(String ip) {
// 1. 先用布隆过滤器快速过滤(绝对不存在直接返回)
if (!ipBlacklistFilter.contains(ip)) {
return false;
}
// 2. 可能存在,再用Redis Set精确验证
Boolean result = redisTemplate.opsForSet().isMember("blacklist:ip:set", ip);
return result != null && result;
}
/**
* 获取布隆过滤器统计信息
*/
public BloomFilterStats getStats() {
BloomFilterStats stats = new BloomFilterStats();
stats.setExpectedInsertions(ipBlacklistFilter.getExpectedInsertions());
stats.setSize(ipBlacklistFilter.getSize());
stats.setHashIterations(ipBlacklistFilter.getHashIterations());
return stats;
}
}
/**
* 布隆过滤器统计信息
*/
@Data
public class BloomFilterStats {
private Long expectedInsertions;
private Long size;
private Integer hashIterations;
}
🔧 拦截器实现
1. IP黑名单拦截器
/**
* IP黑名单拦截器
*/
@Component
@Slf4j
public class IpBlacklistInterceptor implements HandlerInterceptor {
@Autowired
private IpBlacklistService blacklistService;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 1. 获取客户端IP
String ip = getClientIp(request);
// 2. ⚡ 检查是否在黑名单
if (blacklistService.isBlacklisted(ip)) {
log.warn("IP在黑名单中,拒绝访问:ip={}", ip);
// 返回403
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setContentType("application/json;charset=UTF-8");
Result<?> result = Result.fail("您的IP已被封禁,请联系管理员");
response.getWriter().write(JSON.toJSONString(result));
return false;
}
return true;
}
/**
* 获取客户端真实IP
*/
private String getClientIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (StringUtils.isBlank(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
// 如果是多级代理,取第一个IP
if (StringUtils.isNotBlank(ip) && ip.contains(",")) {
ip = ip.split(",")[0].trim();
}
return ip;
}
}
/**
* 注册拦截器
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private IpBlacklistInterceptor ipBlacklistInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(ipBlacklistInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/health", "/actuator/**");
}
}
2. 用户黑名单拦截器
/**
* 用户黑名单拦截器
*/
@Aspect
@Component
@Slf4j
public class UserBlacklistAspect {
@Autowired
private UserBlacklistService blacklistService;
/**
* ⚡ 拦截需要登录的接口
*/
@Before("@annotation(org.springframework.web.bind.annotation.PostMapping) || " +
"@annotation(org.springframework.web.bind.annotation.GetMapping)")
public void checkUserBlacklist() {
// 获取当前用户
Long userId = UserContextHolder.getUserId();
if (userId == null) {
return;
}
// ⚡ 检查是否被拉黑
if (blacklistService.isBlocked(userId)) {
UserBlacklistInfo info = blacklistService.getBlacklistInfo(userId);
String message = "您的账号已被封禁";
if (info != null && StringUtils.isNotBlank(info.getReason())) {
message += ",原因:" + info.getReason();
}
log.warn("用户被拉黑,拒绝访问:userId={}", userId);
throw new BusinessException(message);
}
}
}
📊 管理后台接口
/**
* 黑名单管理Controller
*/
@RestController
@RequestMapping("/admin/blacklist")
@Slf4j
public class BlacklistController {
@Autowired
private IpBlacklistService ipBlacklistService;
@Autowired
private UserBlacklistService userBlacklistService;
/**
* ⚡ 添加IP黑名单
*/
@PostMapping("/ip/add")
public Result<Void> addIpBlacklist(@RequestBody IpBlacklistDTO dto) {
ipBlacklistService.addToBlacklist(
dto.getIp(),
dto.getDuration(),
dto.getUnit());
return Result.success();
}
/**
* 移除IP黑名单
*/
@DeleteMapping("/ip/remove")
public Result<Void> removeIpBlacklist(@RequestParam String ip) {
ipBlacklistService.removeFromBlacklist(ip);
return Result.success();
}
/**
* 查询IP黑名单
*/
@GetMapping("/ip/list")
public Result<Set<String>> getIpBlacklist() {
Set<String> blacklist = ipBlacklistService.getBlacklist();
return Result.success(blacklist);
}
/**
* 检查IP状态
*/
@GetMapping("/ip/check")
public Result<IpStatusVO> checkIp(@RequestParam String ip) {
IpStatusVO status = new IpStatusVO();
status.setIp(ip);
status.setBlacklisted(ipBlacklistService.isBlacklisted(ip));
status.setWhitelisted(ipBlacklistService.isWhitelisted(ip));
status.setHitCount(ipBlacklistService.getHitCount(ip, "blacklist"));
return Result.success(status);
}
/**
* ⚡ 拉黑用户
*/
@PostMapping("/user/block")
public Result<Void> blockUser(@RequestBody UserBlockDTO dto) {
userBlacklistService.blockUser(
dto.getUserId(),
dto.getReason(),
dto.getDuration(),
dto.getUnit());
return Result.success();
}
/**
* 解除拉黑
*/
@PostMapping("/user/unblock")
public Result<Void> unblockUser(@RequestParam Long userId) {
userBlacklistService.unblockUser(userId);
return Result.success();
}
}
/**
* IP状态VO
*/
@Data
public class IpStatusVO {
private String ip;
private Boolean blacklisted;
private Boolean whitelisted;
private Long hitCount;
}
⚡ 自动化管理
1. 定时清理过期黑名单
/**
* 黑名单定时任务
*/
@Component
@Slf4j
public class BlacklistScheduleTask {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* ⚡ 每小时检查并清理过期的临时黑名单
*/
@Scheduled(cron = "0 0 * * * ?")
public void cleanExpiredBlacklist() {
log.info("开始清理过期黑名单");
String pattern = "blacklist:ip:temp:*";
Set<String> keys = redisTemplate.keys(pattern);
if (keys == null || keys.isEmpty()) {
return;
}
int count = 0;
for (String key : keys) {
// 检查是否过期
Long ttl = redisTemplate.getExpire(key);
if (ttl != null && ttl <= 0) {
// 从黑名单Set中移除
String ip = key.replace("blacklist:ip:temp:", "");
redisTemplate.opsForSet().remove("blacklist:ip", ip);
redisTemplate.delete(key);
count++;
}
}
log.info("清理过期黑名单完成:count={}", count);
}
}
2. 自动拉黑恶意IP
/**
* 自动拉黑服务
*/
@Service
@Slf4j
public class AutoBlacklistService {
@Autowired
private IpBlacklistService blacklistService;
@Autowired
private StringRedisTemplate redisTemplate;
/**
* ⚡ 记录IP访问频率
*/
public void recordIpAccess(String ip) {
String key = "ip:access:count:" + ip;
Long count = redisTemplate.opsForValue().increment(key);
if (count == 1) {
// 设置过期时间(1分钟)
redisTemplate.expire(key, 1, TimeUnit.MINUTES);
}
// ⚡ 如果1分钟内访问超过100次,自动拉黑
if (count != null && count > 100) {
blacklistService.addToBlacklist(ip, 24L, TimeUnit.HOURS);
log.warn("IP访问频率过高,自动拉黑:ip={}, count={}", ip, count);
}
}
}
✅ 最佳实践
黑白名单系统最佳实践:
1️⃣ 存储选型:
□ 小数据量(< 10万):Redis Set
□ 大数据量(> 100万):布隆过滤器 + Redis Set
□ 持久化:MySQL备份
2️⃣ 性能优化:
□ 白名单优先(先检查白名单)
□ 布隆过滤器快速过滤
□ 本地缓存(Guava Cache)
□ 批量操作
3️⃣ 功能设计:
□ 临时封禁(自动过期)
□ 等级分类(轻度/重度)
□ 命中统计
□ 操作日志
4️⃣ 安全防护:
□ 获取真实IP(处理代理)
□ 自动拉黑(异常行为)
□ 人工审核
□ 申诉机制
5️⃣ 监控告警:
□ 黑名单数量监控
□ 命中率统计
□ 异常封禁告警
□ 误判检测
🎉 总结
黑白名单系统核心:
1️⃣ Redis Set:O(1)查询,性能极佳
2️⃣ 布隆过滤器:海量数据,空间节省
3️⃣ 白名单优先:先检查白名单
4️⃣ 自动过期:临时封禁自动解除
5️⃣ 命中统计:实时监控拦截效果
记住:黑白名单是访问控制的第一道防线! 🚫
文档编写时间:2025年10月24日
作者:热爱安全防护的访问控制工程师
版本:v1.0
愿每次访问都安全可控! ✨