{"record":{"id":"5f73d07388f8cec2","repo":"dromara/Sa-Token","slug":"code-12203","errorCode":"CODE_12203","errorMessage":"timestamp 超出允许的范围：{timestamp}","messagePattern":"timestamp 超出允许的范围：(.+?)","errorType":"exception","errorClass":"SaSignException","httpStatus":null,"severity":"error","filePath":"sa-token-plugin/sa-token-sign/src/main/java/cn/dev33/satoken/sign/template/SaSignTemplate.java","lineNumber":241,"sourceCode":"\n\t/**\n\t * 判断：指定时间戳与当前时间戳的差距是否在允许的范围内\n\t * @param timestamp 待校验的时间戳\n\t * @return 是否在允许的范围内\n\t */\n\tpublic boolean isValidTimestamp(long timestamp) {\n\t\tlong allowDisparity = getSignConfigOrGlobal().getTimestampDisparity();\n\t\tlong disparity = Math.abs(System.currentTimeMillis() - timestamp);\n\t\treturn allowDisparity == -1 || disparity <= allowDisparity;\n\t}\n\n\t/**\n\t * 校验：指定时间戳与当前时间戳的差距是否在允许的范围内，如果超出则抛出异常\n\t * @param timestamp 待校验的时间戳\n\t */\n\tpublic void checkTimestamp(long timestamp) {\n\t\tif( ! isValidTimestamp(timestamp) ) {\n\t\t\tthrow new SaSignException(\"timestamp 超出允许的范围：\" + timestamp).setCode(SaSignErrorCode.CODE_12203);\n\t\t}\n\t}\n\n\t/**\n\t * 判断：随机字符串 nonce 是否有效。\n\t * \t\t注意：同一 nonce 可以被多次判断有效，不会被缓存\n\t * @param nonce 待判断的随机字符串\n\t * @return 是否有效\n\t */\n\tpublic boolean isValidNonce(String nonce) {\n\t\t// 为空代表无效\n\t\tif(SaFoxUtil.isEmpty(nonce)) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// 校验此 nonce 是否已被使用过\n\t\tString key = splicingNonceSaveKey(nonce);\n\t\treturn SaManager.getSaTokenDao().get(key) == null;","sourceCodeStart":223,"sourceCodeEnd":259,"githubUrl":"https://github.com/dromara/Sa-Token/blob/ac2c7f6e94a78573cf0bcb932dd8b04e68fad189/sa-token-plugin/sa-token-sign/src/main/java/cn/dev33/satoken/sign/template/SaSignTemplate.java#L223-L259","documentation":"Thrown by SaSignTemplate.checkTimestamp when |now - timestamp| exceeds the configured timestampDisparity and disparity is not -1 (unlimited) — code 12203. Server-side API signing includes a freshness window to block replay of intercepted signed requests; a timestamp outside the window is rejected before nonce/sign are even evaluated.","triggerScenarios":"Sending a signed request whose timestamp parameter deviates from server time by more than timestampDisparity milliseconds (default window, commonly 15 min), including stale timestamps from cached/retried requests.","commonSituations":"Client machine clock drift or wrong timezone-epoch handling (seconds vs milliseconds); NTP not running on app servers; requests queued/retried long after signing; disparity configured very tightly.","solutions":["Sync server clocks (NTP/chrony) on both signing and verifying machines","Generate the timestamp at request time in milliseconds (System.currentTimeMillis()), never cache or reuse it","Widen sa-token sign timestamp-disparity config if legitimate latency exceeds the window, or set -1 only if you accept replay risk mitigated by nonce"],"exampleFix":"// before\nparams.put(\"timestamp\", \"1609459200000\"); // fixed/old timestamp\n// after\nparams.put(\"timestamp\", String.valueOf(System.currentTimeMillis()));","handlingStrategy":"validation","validationCode":"long disparity = Math.abs(System.currentTimeMillis() - timestamp);\nif (disparity > signConfig.getTimestampDisparity()) throw new IllegalArgumentException(\"timestamp stale by \" + disparity + \" ms\");","typeGuard":null,"tryCatchPattern":"try { saSignTemplate.checkTimestamp(ts); } catch (SaSignException e) { if (e.getCode() == 12203) return status(401, \"timestamp out of range — check clock sync\"); throw e; }","preventionTips":["Run NTP on all app servers and use System.currentTimeMillis() at signing time","Monitor clock skew between services; alert before it approaches timestampDisparity"],"tags":["api-sign","timestamp","clock-drift","replay-protection"],"backgroundTag":null,"analyzedSha":"ac2c7f6e94a78573cf0bcb932dd8b04e68fad189","analyzedAt":"2026-08-14T14:36:10.271Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}