修复防重放拦截器时间戳兼容与日志
This commit is contained in:
@@ -23,10 +23,12 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
@Slf4j
|
||||
public class ReplayAttackInterceptor implements HandlerInterceptor {
|
||||
|
||||
// 请求时间戳有效期(毫秒):5分钟
|
||||
private static final long TIMESTAMP_VALIDITY = 5 * 60 * 1000;
|
||||
// nonce 有效期(毫秒):10分钟
|
||||
private static final long NONCE_EXPIRE_TIME = 10 * 60 * 1000;
|
||||
// 请求时间戳有效期(毫秒):10分钟,兼容移动端网络抖动与时钟偏差
|
||||
private static final long TIMESTAMP_VALIDITY = 10 * 60 * 1000;
|
||||
// 允许少量未来时间偏移(毫秒):2分钟,兼容秒级时间戳和终端时钟轻微超前
|
||||
private static final long ALLOWED_FUTURE_DRIFT = 2 * 60 * 1000;
|
||||
// nonce 有效期(毫秒):15分钟
|
||||
private static final long NONCE_EXPIRE_TIME = 15 * 60 * 1000;
|
||||
// 最大存储 nonce 数(防止内存溢出)
|
||||
private static final int MAX_NONCES = 100000;
|
||||
|
||||
@@ -91,24 +93,35 @@ public class ReplayAttackInterceptor implements HandlerInterceptor {
|
||||
String timestampStr = request.getHeader("X-Timestamp");
|
||||
String nonce = request.getHeader("X-Nonce");
|
||||
|
||||
String userAgent = request.getHeader("User-Agent");
|
||||
|
||||
// 检查参数是否存在
|
||||
if (timestampStr == null || timestampStr.isEmpty() || nonce == null || nonce.isEmpty()) {
|
||||
String missingHeader = (timestampStr == null || timestampStr.isEmpty()) && (nonce == null || nonce.isEmpty())
|
||||
? "X-Timestamp,X-Nonce"
|
||||
: ((timestampStr == null || timestampStr.isEmpty()) ? "X-Timestamp" : "X-Nonce");
|
||||
log.warn("防重放校验失败: uri={}, method={}, ua={}, missingHeaders={}",
|
||||
requestUri, request.getMethod(), userAgent, missingHeader);
|
||||
writeErrorResponse(response, "缺少安全验证参数");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证时间戳
|
||||
// 验证时间戳(兼容 10 位秒级与 13 位毫秒级)
|
||||
long timestamp;
|
||||
try {
|
||||
timestamp = Long.parseLong(timestampStr);
|
||||
timestamp = normalizeTimestamp(timestampStr);
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("防重放校验失败: uri={}, method={}, ua={}, invalidTimestamp={}, error={}",
|
||||
requestUri, request.getMethod(), userAgent, timestampStr, e.getMessage());
|
||||
writeErrorResponse(response, "无效的时间戳参数");
|
||||
return false;
|
||||
}
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
// 修复:不允许未来时间戳,防止预发送攻击
|
||||
if (timestamp > now || now - timestamp > TIMESTAMP_VALIDITY) {
|
||||
long timeDiff = now - timestamp;
|
||||
if (timeDiff < -ALLOWED_FUTURE_DRIFT || timeDiff > TIMESTAMP_VALIDITY) {
|
||||
log.warn("防重放校验失败: uri={}, method={}, ua={}, timestamp={}, normalizedTimestamp={}, now={}, diffMs={}, validityMs={}",
|
||||
requestUri, request.getMethod(), userAgent, timestampStr, timestamp, now, timeDiff, TIMESTAMP_VALIDITY);
|
||||
writeErrorResponse(response, "请求时间戳无效或已过期");
|
||||
return false;
|
||||
}
|
||||
@@ -117,6 +130,8 @@ public class ReplayAttackInterceptor implements HandlerInterceptor {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
if (usedNonces.containsKey(nonce)) {
|
||||
log.warn("防重放校验失败: uri={}, method={}, ua={}, nonce={}, reason=reused_nonce",
|
||||
requestUri, request.getMethod(), userAgent, nonce);
|
||||
writeErrorResponse(response, "检测到重放攻击,请求已被拒绝");
|
||||
return false;
|
||||
}
|
||||
@@ -135,6 +150,15 @@ public class ReplayAttackInterceptor implements HandlerInterceptor {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private long normalizeTimestamp(String timestampStr) {
|
||||
long parsedTimestamp = Long.parseLong(timestampStr);
|
||||
if (timestampStr.length() == 10) {
|
||||
return parsedTimestamp * 1000;
|
||||
}
|
||||
return parsedTimestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期的 nonce
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user