dromara/Sa-Token · critical · SaSignException
CODE_12202
CODE_12202
Error message
无效签名:{sign} What it means
Thrown by SaSignTemplate.checkSign when createSign(paramsMap) does not equal the provided sign value (code 12202). The server re-computes the digest over the sorted, spliced parameters plus the app's secret key; any mismatch in parameters, key, or algorithm yields '无效签名'.
Source
Thrown at sa-token-plugin/sa-token-sign/src/main/java/cn/dev33/satoken/sign/template/SaSignTemplate.java:301
/**
* 判断:给定的参数 生成的签名是否为有效签名
* @param paramsMap 参数列表
* @param sign 待验证的签名
* @return 签名是否有效
*/
public boolean isValidSign(Map<String, ?> paramsMap, String sign) {
String theSign = createSign(paramsMap);
return theSign.equals(sign);
}
/**
* 校验:给定的参数 生成的签名是否为有效签名,如果签名无效则抛出异常
* @param paramsMap 参数列表
* @param sign 待验证的签名
*/
public void checkSign(Map<String, ?> paramsMap, String sign) {
if( ! isValidSign(paramsMap, sign) ) {
throw new SaSignException("无效签名:" + sign).setCode(SaSignErrorCode.CODE_12202);
}
}
/**
* 判断:参数列表中的 nonce、timestamp、sign 是否均为合法的
* @param paramMap 待校验的请求参数集合
* @return 是否合法
*/
@SuppressWarnings("all")
public boolean isValidParamMap(Map<String, String> paramMap) {
// 获取必须的三个参数
String timestampValue = paramMap.get(timestamp);
String nonceValue = paramMap.get(nonce);
String signValue = paramMap.get(sign);
// 参数非空校验
// 配置isCheckNonce=false时,可以不传 nonce
if(SaFoxUtil.isEmpty(timestampValue) || SaFoxUtil.isEmpty(signValue)) {View on GitHub (pinned to ac2c7f6e94)
Solutions
- Verify the secret key and (for multi-app) the appid→config mapping are identical on both sides
- Reproduce the server's splicing exactly: sort params by key, include timestamp/nonce/appid per config, use the configured digest algo
- Confirm digest-algo spelling is one of md5/sha1/sha256/sha384/sha512 and identical on both sides
- Log the fullStr the server builds (or mirror createSign client-side) and diff it against the client's signed string to find the diverging parameter
Example fix
// before (client forgets timestamp & nonce in the signed string)
String sign = DigestUtils.md5Hex(bizParamsOnly);
// after
TreeMap<String,String> p = new TreeMap<>(bizParams);
p.put("timestamp", ts); p.put("nonce", nonce);
String sign = SaSignTemplateUtil.createSign(p, secretKey); Defensive patterns
Strategy: try-catch
Validate before calling
String expected = saSignTemplate.createSign(paramsMap);
if (!expected.equals(sign)) throw new SecurityException("signature mismatch — check key, algo, and param splicing"); Try / catch
try { saSignTemplate.checkSign(paramsMap, sign); } catch (SaSignException e) { if (e.getCode() == 12202) { log.warn("sign mismatch, params={}", paramsMap); return status(401, "invalid signature"); } throw e; } Prevention
- Mirror the server's createSign logic in the client and unit-test both against the same fixture
- Keep secret keys in synced secret storage; rotate on both sides simultaneously
- Log the spliced fullStr on failure once to diff client vs server parameter sets
When it happens
Trigger: Signature computed with a different secret key, different parameter set (extra/missing params, different sort or exclusion rules like timestamp/nonce/appid handling), different digest algorithm, or wrong string splicing on the client versus the server.
Common situations: Secret key mismatch between caller and server (or per-appid config wrong in SaSignMany); client signs business params but omits/incorrectly orders timestamp & nonce; hyphenated algo name ('sha-256') failing algo dispatch; params transformed in transit (charset, URL decoding, trailing whitespace); GET vs POST parameter merge differences.
Related errors
AI-assisted analysis of dromara/Sa-Token@ac2c7f6e94 (2026-08-14).
Data as JSON: /api/errors/d5d7335580ba26e1.
Report an issue: GitHub.