{"record":{"id":"7739e3a86bb17df9","repo":"jeecgboot/JeecgBoot","slug":"error-7739e3","errorCode":null,"errorMessage":"签名验证失败，请求时效性验证失败！","messagePattern":"签名验证失败，请求时效性验证失败！","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/interceptor/SignAuthInterceptor.java","lineNumber":102,"sourceCode":"                log.error(\"Sign签名校验失败，时间戳为空！\");\n                throw new IllegalArgumentException(\"Sign签名校验失败，请求参数不完整！\");\n            }\n\n            //客户端时间\n            Long clientTimestamp = Long.parseLong(xTimestamp);\n\n            int length = 14;\n            int length1000 = 1000;\n            //1.校验签名时间（兼容X_TIMESTAMP的新老格式）\n            if (xTimestamp.length() == length) {\n                //a. X_TIMESTAMP格式是 yyyyMMddHHmmss (例子：20220308152143)\n                long currentTimestamp = DateUtils.getCurrentTimestamp();\n                long timeDiff = currentTimestamp - clientTimestamp;\n                log.debug(\"时间戳验证(yyyyMMddHHmmss): 时间差{}秒\", timeDiff);\n                \n                if (timeDiff > MAX_EXPIRE) {\n                    log.error(\"时间戳已过期: {}秒 > {}秒\", timeDiff, MAX_EXPIRE);\n                    throw new IllegalArgumentException(\"签名验证失败，请求时效性验证失败！\");\n                }\n            } else {\n                //b. X_TIMESTAMP格式是 时间戳 (例子：1646552406000)\n                long currentTime = System.currentTimeMillis();\n                long timeDiff = currentTime - clientTimestamp;\n                long maxExpireMs = MAX_EXPIRE * length1000;\n                log.debug(\"时间戳验证(Unix): 时间差{}ms\", timeDiff);\n                \n                if (timeDiff > maxExpireMs) {\n                    log.error(\"时间戳已过期: {}ms > {}ms\", timeDiff, maxExpireMs);\n                    throw new IllegalArgumentException(\"签名验证失败，请求时效性验证失败！\");\n                }\n            }\n\n            //2.校验签名\n            boolean isSigned = SignUtil.verifySign(allParams,headerSign);\n\n            if (isSigned) {","sourceCodeStart":84,"sourceCodeEnd":120,"githubUrl":"https://github.com/jeecgboot/JeecgBoot/blob/96fb33f5ec68516da0b0147da06b2eb0419e063a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/interceptor/SignAuthInterceptor.java#L84-L120","documentation":"This error is thrown when the request timestamp (in yyyyMMddHHmmss format, e.g. 20220308152143) indicates the request is older than the allowed MAX_EXPIRE window of 5 minutes (300 seconds). The interceptor computes the difference between the server's current timestamp and the client timestamp; if timeDiff > MAX_EXPIRE (300 seconds), the request is rejected as expired.","triggerScenarios":"A client sends a request with X-TIMESTAMP in the 14-digit yyyyMMddHHmmss format where the value is more than 5 minutes old relative to the server's clock. This is detected at line 100: `if (timeDiff > MAX_EXPIRE)`.","commonSituations":"Client and server clocks are significantly out of sync. The client pre-computed and cached a signed request but delayed sending it past the 5-minute window. Network latency combined with a timestamp generated too early. Replay attempts using old captured requests.","solutions":["Synchronize client and server clocks using NTP/chrony to minimize clock drift.","Generate the X-TIMESTAMP at the moment of sending the request, not when building the UI or pre-computing parameters.","If legitimate use cases require longer validity, increase MAX_EXPIRE in SignAuthInterceptor (line 32), but be aware this widens the replay window.","Debug by logging the server's current timestamp alongside the client timestamp to identify the drift."],"exampleFix":"// before\nString timestamp = \"20240101120000\"; // hardcoded or cached timestamp\n\n// after\n// Generate timestamp at send time using server-equivalent format\nSimpleDateFormat sdf = new SimpleDateFormat(\"yyyyMMddHHmmss\");\nString timestamp = sdf.format(new Date());\nconn.setRequestProperty(\"X-TIMESTAMP\", timestamp);","handlingStrategy":"validation","validationCode":"// Client-side: validate timestamp freshness before sending\nlong serverTimeEstimate = System.currentTimeMillis();\nSimpleDateFormat sdf = new SimpleDateFormat(\"yyyyMMddHHmmss\");\nString timestamp = sdf.format(new Date());\n// Verify timestamp is within the last 4 minutes to allow for network latency\nlong tsMillis = sdf.parse(timestamp).getTime();\nif (System.currentTimeMillis() - tsMillis > 240_000) {\n    // Regenerate — clock may have drifted\n    timestamp = sdf.format(new Date());\n}","typeGuard":"// Server-side helper: check timestamp validity\npublic static boolean isTimestampValid(String xTimestamp) {\n    if (xTimestamp == null || xTimestamp.length() != 14) return false;\n    try {\n        long clientTs = Long.parseLong(xTimestamp);\n        long currentTs = Long.parseLong(DateUtils.getCurrentTimestamp() + \"\");\n        return (currentTs - clientTs) <= 300; // MAX_EXPIRE seconds\n    } catch (NumberFormatException e) {\n        return false;\n    }\n}","tryCatchPattern":"// In the client SDK, implement automatic timestamp refresh\nint maxRetries = 1;\nfor (int i = 0; i <= maxRetries; i++) {\n    request.setHeader(\"X-TIMESTAMP\", generateTimestamp());\n    request.setHeader(\"X-SIGN\", computeSign(params, secret));\n    try {\n        return execute(request);\n    } catch (SignatureExpiredException e) {\n        if (i == maxRetries) throw e;\n    }\n}","preventionTips":["Generate the timestamp immediately before sending the HTTP request","Synchronize client clock with NTP","Never cache or reuse timestamps across requests","Build timestamps at the transport layer (HTTP client interceptor), not the business logic layer"],"tags":["signature","security","timestamp","replay-protection"],"backgroundTag":null,"analyzedSha":"96fb33f5ec68516da0b0147da06b2eb0419e063a","analyzedAt":"2026-08-14T00:04:16.786Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}