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
- 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.
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
- Send all required signature headers with exact names
- Register the correct appSecret for each appId
- Sync server/client clocks and signing string format
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.