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

  1. Re-sign each request attempt with a fresh nonce and timestamp instead of replaying the same signed string
  2. Disable or debounce duplicate submissions on the client (button disable, request dedup)
  3. 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

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.