YunaiV/ruoyi-vue-pro · error · ServiceException

400

400

Error message

签名不正确

What it means

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.

Source

Thrown at yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/aop/ApiSignatureAspect.java:50

 */
@Aspect
@Slf4j
@AllArgsConstructor
public class ApiSignatureAspect {

    private final ApiSignatureRedisDAO signatureRedisDAO;

    @Before("@annotation(signature)")
    public void beforePointCut(JoinPoint joinPoint, ApiSignature signature) {
        // 1. 验证通过,直接结束
        if (verifySignature(signature, Objects.requireNonNull(ServletUtils.getRequest()))) {
            return;
        }

        // 2. 验证不通过,抛出异常
        log.error("[beforePointCut][方法{} 参数({}) 签名失败]", joinPoint.getSignature().toString(),
                joinPoint.getArgs());
        throw new ServiceException(BAD_REQUEST.getCode(),
                StrUtil.blankToDefault(signature.message(), BAD_REQUEST.getMsg()));
    }

    public boolean verifySignature(ApiSignature signature, HttpServletRequest request) {
        // 1.1 校验 Header
        if (!verifyHeaders(signature, request)) {
            return false;
        }
        // 1.2 校验 appId 是否能获取到对应的 appSecret
        String appId = request.getHeader(signature.appId());
        String appSecret = signatureRedisDAO.getAppSecret(appId);
        Assert.notNull(appSecret, "[appId({})] 找不到对应的 appSecret", appId);

        // 2. 校验签名【重要!】
        String clientSignature = request.getHeader(signature.sign()); // 客户端签名
        String serverSignatureString = buildSignatureString(signature, request, appSecret); // 服务端签名字符串
        String serverSignature = DigestUtil.sha256Hex(serverSignatureString); // 服务端签名
        if (ObjUtil.notEqual(clientSignature, serverSignature)) {

View on GitHub (pinned to 0418084e22)

Solutions

  1. Confirm the client sends all required headers (appId, timestamp, nonce, sign) with the exact names from the @ApiSignature annotation.
  2. Ensure the appId is registered in ApiSignatureRedisDAO with the correct appSecret on both sides.
  3. Match the server's signature-string building (method, path, headers, body, secret) exactly; use the provided client SDK if available.
  4. Sync server/client clocks and keep timestamp within the allowed window.

Example fix

// before: client signs with a stale secret or omits nonce
sign = sha256(appId + timestamp + body, oldSecret)
// after: include nonce and use the registered secret
sign = sha256(buildSignatureString(appId, timestamp, nonce, body), currentSecret)
Defensive patterns

Strategy: validation

Validate before calling

for (String h : new String[]{signature.appId(), signature.timestamp(), signature.nonce(), signature.sign()}) {
    if (request.getHeader(h) == null) throw new IllegalArgumentException("Missing signature header: " + h);
}

Type guard

static boolean hasAllSignatureHeaders(ApiSignature s, HttpServletRequest r) {
    return r.getHeader(s.appId())!=null && r.getHeader(s.timestamp())!=null && r.getHeader(s.nonce())!=null && r.getHeader(s.sign())!=null;
}

Try / catch

try { aspect.beforePointCut(joinPoint, signature); }
catch (ServiceException e) { if (e.getCode()==400) return ResponseEntity.badRequest().body("invalid signature"); throw e; }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of YunaiV/ruoyi-vue-pro@0418084e22 (2026-08-14). Data as JSON: /api/errors/c93315f367a80b2f. Report an issue: GitHub.