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