YunaiV/ruoyi-vue-pro · warning · ServiceException
900
900
Error message
存在重复请求
What it means
After a valid signature, ApiSignatureAspect stores the request nonce in Redis (setNonce) with TTL = timeout*2 to prevent replay. If setNonce returns false, the nonce was already seen within the window, so the request is a replay and it throws ServiceException(REPEATED_REQUESTS code 900, '存在重复请求'). It is the anti-replay leg of the signature scheme.
Source
Thrown at yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/signature/core/aop/ApiSignatureAspect.java:77
// 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)) {
return false;
}
// 3. 将 nonce 记入缓存,防止重复使用(重点二:此处需要将 ttl 设定为允许 timestamp 时间差的值 x 2 )
String nonce = request.getHeader(signature.nonce());
if (BooleanUtil.isFalse(signatureRedisDAO.setNonce(appId, nonce, signature.timeout() * 2, signature.timeUnit()))) {
String timestamp = request.getHeader(signature.timestamp());
log.info("[verifySignature][appId({}) timestamp({}) nonce({}) sign({}) 存在重复请求]", appId, timestamp, nonce, clientSignature);
throw new ServiceException(GlobalErrorCodeConstants.REPEATED_REQUESTS.getCode(), "存在重复请求");
}
return true;
}
/**
* 校验请求头加签参数
* <p>
* 1. appId 是否为空
* 2. timestamp 是否为空,请求是否已经超时,默认 10 分钟
* 3. nonce 是否为空,随机数是否 10 位以上,是否在规定时间内已经访问过了
* 4. sign 是否为空
*
* @param signature signature
* @param request request
* @return 是否校验 Header 通过
*/
private boolean verifyHeaders(ApiSignature signature, HttpServletRequest request) {
// 1. 非空校验View on GitHub (pinned to 0418084e22)
Solutions
- Generate a fresh, unique nonce (>=10 chars, random) for every request, never reuse.
- Ensure retries also regenerate the nonce and re-sign.
- Increase @ApiSignature timeout if legitimate latency exceeds the nonce window.
- On the client, treat code 900 'duplicate' as terminal for that request id.
Example fix
// before: static/cached nonce reused across retries String nonce = "abc123"; // after: fresh nonce per request String nonce = RandomUtil.randomString(16);
Defensive patterns
Strategy: validation
Validate before calling
String nonce = request.getHeader(signature.nonce());
if (nonce == null || nonce.length() < 10) throw new IllegalArgumentException("nonce missing or too short");
if (!signatureRedisDAO.setNonce(appId, nonce, signature.timeout()*2, signature.timeUnit()))
throw new ServiceException(REPEATED_REQUESTS); Type guard
static boolean isValidNonce(String n) { return n != null && n.length() >= 10; } Try / catch
try { aspect.beforePointCut(joinPoint, signature); }
catch (ServiceException e) { if (e.getCode()==900 && "存在重复请求".equals(e.getMessage())) return ResponseEntity.status(409).body("replay"); throw e; } Prevention
- Generate a fresh random nonce per request
- Never reuse nonces across retries
- Increase @ApiSignature timeout if latency is high
When it happens
Trigger: The same request (same appId+nonce+sign) is sent twice within 2*timeout; a client reuses a nonce instead of generating a fresh random one; an attacker replays a captured request; the nonce TTL is shorter than real network latency so legit retries collide.
Common situations: Client caching/reusing nonces; retry middleware resending identical requests; duplicate network delivery causing the same nonce twice.
Related errors
- 400
- LoginUser(%d) Table(%s/%s) 未返回数据权限
- AreaUtils 初始化失败
- IPUtils 初始化失败
- TenantContextHolder 不存在租户编号!可参考文档:{}
AI-assisted analysis of YunaiV/ruoyi-vue-pro@0418084e22 (2026-08-14).
Data as JSON: /api/errors/710c4017a32f9e73.
Report an issue: GitHub.