dromara/Sa-Token · error · SaSignException
此 nonce 已被使用过,不可重复使用:{nonce}
Error message
此 nonce 已被使用过,不可重复使用:{nonce} What it means
Thrown by SaSignTemplate.checkNonce when the nonce's cache key already exists in the DAO, i.e. this exact nonce value passed a check before and is still within its save window (saveNonceExpire * 2 + 2 seconds). This enforces single-use nonces so an intercepted signed request cannot be replayed verbatim.
Source
Thrown at sa-token-plugin/sa-token-sign/src/main/java/cn/dev33/satoken/sign/template/SaSignTemplate.java:276
String key = splicingNonceSaveKey(nonce);
return SaManager.getSaTokenDao().get(key) == null;
}
/**
* 校验:随机字符串 nonce 是否有效,如果无效则抛出异常。
* 注意:同一 nonce 只可以被校验通过一次,校验后将保存在缓存中,再次校验将无法通过
* @param nonce 待校验的随机字符串
*/
public void checkNonce(String nonce) {
// 为空代表无效
if(SaFoxUtil.isEmpty(nonce)) {
throw new SaSignException("nonce 为空,无效");
}
// 校验此 nonce 是否已被使用过
String key = splicingNonceSaveKey(nonce);
if(SaManager.getSaTokenDao().get(key) != null) {
throw new SaSignException("此 nonce 已被使用过,不可重复使用:" + nonce);
}
// 校验通过后,将此 nonce 保存在缓存中,保证下次校验无法通过
SaManager.getSaTokenDao().set(key, nonce, getSignConfigOrGlobal().getSaveNonceExpire() * 2 + 2);
}
/**
* 判断:给定的参数 生成的签名是否为有效签名
* @param paramsMap 参数列表
* @param sign 待验证的签名
* @return 签名是否有效
*/
public boolean isValidSign(Map<String, ?> paramsMap, String sign) {
String theSign = createSign(paramsMap);
return theSign.equals(sign);
}
/**View on GitHub (pinned to ac2c7f6e94)
Solutions
- Re-sign each request attempt with a fresh nonce and timestamp instead of replaying the same signed string
- Disable or debounce duplicate submissions on the client (button disable, request dedup)
- If retries must be identical, make the operation idempotent server-side and accept that the duplicate signed call is rejected
Example fix
// before String signedQuery = buildSignedQueryOnce(params); // reused for every retry http.post(signedQuery); // after http.post(buildSignedQuery(params)); // re-signs: new nonce + timestamp each attempt
Defensive patterns
Strategy: retry
Validate before calling
String key = "satoken:sign:nonce:" + nonce;
if (SaManager.getSaTokenDao().get(key) != null) throw new IllegalStateException("nonce already used — re-sign with a new nonce"); Try / catch
try { saSignTemplate.checkNonce(nonce); } catch (SaSignException e) { if (e.getMessage().contains("已被使用过")) { reSignWithFreshNonceAndTimestamp(); retry(request); } else throw e; } Prevention
- Re-sign with a new nonce and timestamp on every retry attempt
- Disable automatic retries at the HTTP-client level for signed calls, or hook retry to re-sign
When it happens
Trigger: Sending the identical signed request twice — network retry, client-side duplicate submit, or an attacker replaying a captured request — within the nonce retention window.
Common situations: HTTP client automatic retries on timeout; user double-clicks submit; idempotent background jobs reusing a cached signed request; legitimate retry logic that signs once and sends many times.
Related errors
AI-assisted analysis of dromara/Sa-Token@ac2c7f6e94 (2026-08-14).
Data as JSON: /api/errors/93b18322843e15a9.
Report an issue: GitHub.