{"record":{"id":"1ce0e046f3c056bd","repo":"jeecgboot/JeecgBoot","slug":"sign-1ce0e0","errorCode":null,"errorMessage":"Sign签名校验失败！","messagePattern":"Sign签名校验失败！","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":124,"sourceCode":"                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) {\n                log.debug(\"签名验证通过\");\n            } else {\n                log.error(\"签名验证失败, 参数: {}\", allParams);\n                throw new IllegalArgumentException(\"Sign签名校验失败！\");\n            }\n        } catch (IllegalArgumentException e) {\n            // 重新抛出签名验证异常\n            throw e;\n        } catch (Exception e) {\n            // 包装其他异常（如IOException）\n            log.error(\"签名验证异常: {}\", e.getMessage());\n            throw new IllegalArgumentException(\"Sign签名校验失败：\" + e.getMessage());\n        }\n    }\n\n}\n","sourceCodeStart":106,"sourceCodeEnd":137,"githubUrl":"https://github.com/jeecgboot/JeecgBoot/blob/96fb33f5ec68516da0b0147da06b2eb0419e063a/jeecg-boot/jeecg-boot-base-core/src/main/java/org/jeecg/config/sign/interceptor/SignAuthInterceptor.java#L106-L137","documentation":"This error is thrown when the actual signature verification fails — the X-SIGN header does not match the computed MD5 hash of the sorted request parameters concatenated with the signatureSecret. SignUtil.verifySign compares headerSign with getParamsSign(allParams), and if they differ, the request is rejected. This is the core cryptographic mismatch error.","triggerScenarios":"A client sends a request with a valid X-TIMESTAMP (not expired) but the X-SIGN header value does not equal the MD5 of (JSON-sorted-params + signatureSecret). Common causes: wrong secret, wrong parameter serialization, parameters sent in a different format than expected, or the _t timestamp parameter not being removed from the signature computation.","commonSituations":"The jeecg.signatureSecret was changed on the server but the client still uses the old secret. The client serializes parameters differently (e.g., includes _t, uses different JSON field order, omits body parameters). A BodyReaderHttpServletRequestWrapper that failed to capture body params leads to an incomplete parameter map. URL path variables (x-path-variable) not included in the signature.","solutions":["Verify that the client uses the exact same jeecg.signatureSecret value configured in the server's application.yml.","Ensure the client sorts all parameters alphabetically, serializes to JSON, appends the secret, then MD5-hashes and uppercases the result — matching SignUtil.getParamsSign().","Confirm that the _t parameter is excluded from signing (SignUtil removes it at line 53) and that body parameters (from @RequestBody) are included.","Enable debug logging (log.debug) on SignUtil and SignAuthInterceptor to compare the parameter maps and computed signs between client and server."],"exampleFix":"// before (client) — wrong: signing raw params without JSON serialization\nString sign = md5(params.toString() + secret);\n\n// after — correct: match server's algorithm\nTreeMap<String,String> sortedParams = new TreeMap<>(params);\nsortedParams.remove(\"_t\");\nString jsonStr = JSON.toJSONString(sortedParams);\nString sign = DigestUtils.md5DigestAsHex((jsonStr + secret).getBytes(\"UTF-8\")).toUpperCase();","handlingStrategy":"validation","validationCode":"// Client-side: replicate the exact server signing algorithm\npublic static String computeSign(SortedMap<String, String> params, String secret) {\n    params.remove(\"_t\"); // server removes this\n    String jsonStr = com.alibaba.fastjson.JSON.toJSONString(params);\n    String raw = jsonStr + secret;\n    return org.springframework.util.DigestUtils\n        .md5DigestAsHex(raw.getBytes(java.nio.charset.StandardCharsets.UTF_8))\n        .toUpperCase();\n}","typeGuard":"// Verify sign matches before sending (self-check)\npublic static boolean verifySelfSign(SortedMap<String, String> params, String headerSign, String secret) {\n    if (params == null || headerSign == null || headerSign.isEmpty()) return false;\n    params.remove(\"_t\");\n    String jsonStr = JSON.toJSONString(params);\n    String expected = DigestUtils.md5DigestAsHex((jsonStr + secret).getBytes(StandardCharsets.UTF_8)).toUpperCase();\n    return headerSign.equals(expected);\n}","tryCatchPattern":"try {\n    response = httpClient.execute(request);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"Sign签名校验失败！\")) {\n        log.error(\"Signature mismatch. Enable debug logging to compare param maps.\");\n        // Do NOT retry with same signature — regenerate from scratch\n    }\n    throw e;\n}","preventionTips":["Use the exact same JSON serializer (FastJSON) as the server — Gson/Jackson may order keys differently","Ensure the signatureSecret is identical on client and server","Include body parameters (from @RequestBody) in the parameter map, not just URL params","Remove the _t parameter from signing on the client side","Always uppercase the final MD5 hex string"],"tags":["signature","security","authentication","md5","crypto"],"backgroundTag":null,"analyzedSha":"96fb33f5ec68516da0b0147da06b2eb0419e063a","analyzedAt":"2026-08-14T00:04:16.786Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}