优化黑名单验证
This commit is contained in:
@@ -10,26 +10,16 @@ public interface PreRedisKeyName {
|
|||||||
/**
|
/**
|
||||||
* 防刷key名
|
* 防刷key名
|
||||||
*/
|
*/
|
||||||
String PREVENT_REPLAY = "preventReplay:ip:";
|
String PREVENT_REPLAY = "preventReplay:hash:";
|
||||||
/**
|
|
||||||
* 黑名单ip
|
|
||||||
*/
|
|
||||||
String BLACK_LIST = "blackList:ip:";
|
|
||||||
/**
|
|
||||||
* 单次执行任务Id
|
|
||||||
*/
|
|
||||||
String EXECUTE_SINGLE_TASK = "execute:single-task-id:";
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 黑名单指纹哈希值
|
||||||
|
*/
|
||||||
|
String BLACK_LIST = "blackList:hash:";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* token Key名 前缀
|
* token Key名 前缀
|
||||||
*/
|
*/
|
||||||
String LOGIN_TOKEN = "login:token:";
|
String LOGIN_TOKEN = "login:token:";
|
||||||
|
|
||||||
/**
|
|
||||||
* 图片验证码Key名 前缀
|
|
||||||
*/
|
|
||||||
String LOGIN_IMAGES_CODE_UUID = "login:images:uuid:";
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import org.jiayunet.tool.server.RedisServerTool;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 防止高频重复请求
|
* 防止高频重复请求
|
||||||
*
|
*
|
||||||
* @author zk
|
* @author zk
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
@@ -43,36 +43,54 @@ public class PreventReplayInterceptor implements HandlerInterceptor {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private RedisServerTool redisServerTool;
|
private RedisServerTool redisServerTool;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成指纹:IP + User-Agent + Accept-Language
|
||||||
|
*/
|
||||||
|
private String generateFingerprint(HttpServletRequest request) {
|
||||||
|
String ip = HttpIpTool.gteRealIP(request);
|
||||||
|
String ua = request.getHeader("User-Agent");
|
||||||
|
String lang = request.getHeader("Accept-Language");
|
||||||
|
if (ua == null) ua = "";
|
||||||
|
if (lang == null) lang = "";
|
||||||
|
String raw = ip + "|" + ua + "|" + lang;
|
||||||
|
return Integer.toHexString(raw.hashCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||||
// 关闭时 不执行
|
// 若关闭直接放行
|
||||||
if (!ifOpen) {
|
if (!Boolean.TRUE.equals(ifOpen)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
String redisKeyName = PreRedisKeyName.PREVENT_REPLAY + HttpIpTool.gteRealIP(request);
|
// 使用指纹作为 Redis 键
|
||||||
|
String redisKeyName = PreRedisKeyName.PREVENT_REPLAY + generateFingerprint(request);
|
||||||
|
|
||||||
// 获取redis中的数据
|
// 读取计数信息
|
||||||
RedisPreventReplayInfo replayInfo = redisServerTool.get(redisKeyName, RedisPreventReplayInfo.class);
|
RedisPreventReplayInfo replayInfo = redisServerTool.get(redisKeyName, RedisPreventReplayInfo.class);
|
||||||
|
|
||||||
replayInfo = Objects.nonNull(replayInfo) ? replayInfo : new RedisPreventReplayInfo();
|
replayInfo = Objects.nonNull(replayInfo) ? replayInfo : new RedisPreventReplayInfo();
|
||||||
|
|
||||||
// 计算时间差 并更新数据
|
// 检查时间窗口是否已过
|
||||||
Duration duration = Duration.between(replayInfo.getLastTiming(), Instant.now());
|
Duration duration = Duration.between(replayInfo.getLastTiming(), Instant.now());
|
||||||
long seconds = duration.getSeconds();
|
if (duration.getSeconds() > intervalTime) {
|
||||||
replayInfo = seconds <= intervalTime ? replayInfo : new RedisPreventReplayInfo();
|
replayInfo = new RedisPreventReplayInfo();
|
||||||
|
|
||||||
// 增加次数 写回redis
|
|
||||||
replayInfo.setFrequency(replayInfo.getFrequency() + 1);
|
|
||||||
redisServerTool.set(redisKeyName, replayInfo);
|
|
||||||
|
|
||||||
boolean type = replayInfo.getFrequency() <= limitNumber;
|
|
||||||
|
|
||||||
// 加黑
|
|
||||||
if (!type) {
|
|
||||||
redisServerTool.set(PreRedisKeyName.BLACK_LIST + request.getRemoteAddr(), "高频请求", 1, TimeUnit.DAYS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return type;
|
// 更新最近一次请求时间并计数
|
||||||
|
replayInfo.setLastTiming(Instant.now());
|
||||||
|
replayInfo.setFrequency(replayInfo.getFrequency() + 1);
|
||||||
|
|
||||||
|
// 写回 Redis,设置 TTL 与窗口保持一致,防止孤儿键占用空间
|
||||||
|
redisServerTool.set(redisKeyName, replayInfo, intervalTime, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
boolean allowed = replayInfo.getFrequency() <= limitNumber;
|
||||||
|
if (!allowed) {
|
||||||
|
// 加入黑名单,使用相同指纹键避免误拦
|
||||||
|
redisServerTool.set(PreRedisKeyName.BLACK_LIST + generateFingerprint(request), "高频请求", 1, TimeUnit.DAYS);
|
||||||
|
response.setStatus(HttpServletResponse.SC_CONFLICT);
|
||||||
|
}
|
||||||
|
return allowed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user