{"record":{"id":"c93315f367a80b2f","repo":"YunaiV/ruoyi-vue-pro","slug":"400","errorCode":"400","errorMessage":"签名不正确","messagePattern":"签名不正确","errorType":"exception","errorClass":"ServiceException","httpStatus":null,"severity":"error","filePath":"yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/aop/ApiSignatureAspect.java","lineNumber":50,"sourceCode":" */\n@Aspect\n@Slf4j\n@AllArgsConstructor\npublic class ApiSignatureAspect {\n\n    private final ApiSignatureRedisDAO signatureRedisDAO;\n\n    @Before(\"@annotation(signature)\")\n    public void beforePointCut(JoinPoint joinPoint, ApiSignature signature) {\n        // 1. 验证通过，直接结束\n        if (verifySignature(signature, Objects.requireNonNull(ServletUtils.getRequest()))) {\n            return;\n        }\n\n        // 2. 验证不通过，抛出异常\n        log.error(\"[beforePointCut][方法{} 参数({}) 签名失败]\", joinPoint.getSignature().toString(),\n                joinPoint.getArgs());\n        throw new ServiceException(BAD_REQUEST.getCode(),\n                StrUtil.blankToDefault(signature.message(), BAD_REQUEST.getMsg()));\n    }\n\n    public boolean verifySignature(ApiSignature signature, HttpServletRequest request) {\n        // 1.1 校验 Header\n        if (!verifyHeaders(signature, request)) {\n            return false;\n        }\n        // 1.2 校验 appId 是否能获取到对应的 appSecret\n        String appId = request.getHeader(signature.appId());\n        String appSecret = signatureRedisDAO.getAppSecret(appId);\n        Assert.notNull(appSecret, \"[appId({})] 找不到对应的 appSecret\", appId);\n\n        // 2. 校验签名【重要！】\n        String clientSignature = request.getHeader(signature.sign()); // 客户端签名\n        String serverSignatureString = buildSignatureString(signature, request, appSecret); // 服务端签名字符串\n        String serverSignature = DigestUtil.sha256Hex(serverSignatureString); // 服务端签名\n        if (ObjUtil.notEqual(clientSignature, serverSignature)) {","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/YunaiV/ruoyi-vue-pro/blob/0418084e222612af2fc1141f566af454f9236ab1/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/aop/ApiSignatureAspect.java#L32-L68","documentation":"ApiSignatureAspect verifies an HMAC/SHA-256 request signature on @ApiSignature endpoints. If verifySignature returns false (missing/invalid headers, wrong appId, unknown appSecret, bad timestamp, or computed sign != client sign), it throws ServiceException(BAD_REQUEST code 400, default '签名不正确'). It protects APIs from tampering/forgery.","triggerScenarios":"Client omits or misnames signature headers (appId, timestamp, nonce, sign); appSecret not registered or mismatched; client uses a different signing algorithm/string format than the server; clock skew beyond the timestamp window; replay after nonce expiry.","commonSituations":"Third-party integration with the wrong appSecret; client library version mismatch changing the signature string; clock drift between client and server; headers stripped by a proxy.","solutions":["Confirm the client sends all required headers (appId, timestamp, nonce, sign) with the exact names from the @ApiSignature annotation.","Ensure the appId is registered in ApiSignatureRedisDAO with the correct appSecret on both sides.","Match the server's signature-string building (method, path, headers, body, secret) exactly; use the provided client SDK if available.","Sync server/client clocks and keep timestamp within the allowed window."],"exampleFix":"// before: client signs with a stale secret or omits nonce\nsign = sha256(appId + timestamp + body, oldSecret)\n// after: include nonce and use the registered secret\nsign = sha256(buildSignatureString(appId, timestamp, nonce, body), currentSecret)","handlingStrategy":"validation","validationCode":"for (String h : new String[]{signature.appId(), signature.timestamp(), signature.nonce(), signature.sign()}) {\n    if (request.getHeader(h) == null) throw new IllegalArgumentException(\"Missing signature header: \" + h);\n}","typeGuard":"static boolean hasAllSignatureHeaders(ApiSignature s, HttpServletRequest r) {\n    return r.getHeader(s.appId())!=null && r.getHeader(s.timestamp())!=null && r.getHeader(s.nonce())!=null && r.getHeader(s.sign())!=null;\n}","tryCatchPattern":"try { aspect.beforePointCut(joinPoint, signature); }\ncatch (ServiceException e) { if (e.getCode()==400) return ResponseEntity.badRequest().body(\"invalid signature\"); throw e; }","preventionTips":["Send all required signature headers with exact names","Register the correct appSecret for each appId","Sync server/client clocks and signing string format"],"tags":["yudao","api-signature","security","authentication"],"backgroundTag":null,"analyzedSha":"0418084e222612af2fc1141f566af454f9236ab1","analyzedAt":"2026-08-14T00:56:18.412Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}