dromara/Sa-Token · critical · SaJwtException
30202
30202
Error message
jwt 签名无效:
What it means
Thrown by SaJwtTemplate.parseToken when jwt.setSigner(createSigner(keyt)).verify() returns false: the token parses fine but its signature does not match the configured secret. Code 30202 marks a signature verification failure — the token was signed with a different key or tampered with.
Source
Thrown at sa-token-plugin/sa-token-jwt/src/main/java/cn/dev33/satoken/jwt/SaJwtTemplate.java:193
// 如果token为null
if(token == null) {
throw new SaJwtException("jwt 字符串不可为空");
}
// 解析
JWT jwt;
try {
jwt = JWT.of(token);
} catch (JWTException | JSONException e) {
throw new SaJwtException("jwt 解析失败:" + token, e).setCode(SaJwtErrorCode.CODE_30201);
}
JSONObject payloads = jwt.getPayloads();
// 校验 Token 签名
boolean verify = jwt.setSigner(createSigner(keyt)).verify();
if( ! verify) {
throw new SaJwtException("jwt 签名无效:" + token).setCode(SaJwtErrorCode.CODE_30202);
}
// 校验 loginType
if( ! Objects.equals(loginType, payloads.getStr(LOGIN_TYPE))) {
throw new SaJwtException("jwt loginType 无效:" + token).setCode(SaJwtErrorCode.CODE_30203);
}
// 校验 Token 有效期
if(isCheckTimeout) {
Long effTime = payloads.getLong(EFF, 0L);
if(effTime != NEVER_EXPIRE) {
if(effTime == null || effTime < System.currentTimeMillis()) {
throw new SaJwtException("jwt 已过期:" + token).setCode(SaJwtErrorCode.CODE_30204);
}
}
}
// 返回 View on GitHub (pinned to ac2c7f6e94)
Solutions
- Ensure the verifying service uses exactly the same jwt-secret-key (and key-type/algorithm settings) as the issuing service
- If the secret was rotated, re-issue tokens or keep old tokens valid via a grace period with the previous secret
- Check for environment drift: print the config source / compare env vars on both sides (name only, never the value)
Example fix
# before
# issuer: jwt-secret-key: ${JWT_SECRET_ISSUE}
# verifier: jwt-secret-key: ${JWT_SECRET} # different value -> 30202
# after
# both services read the same variable
sa-token:
jwt-secret-key: ${JWT_SECRET} Defensive patterns
Strategy: try-catch
Try / catch
catch (SaJwtException e) {
if (e.getCode() == SaJwtErrorCode.CODE_30202) {
// 401: reject token; possible tampering or secret drift — alert if unexpected
}
} Prevention
- Share the jwt secret via one secret store across services; never duplicate literals
- When rotating secrets, overlap old/new acceptance windows or force re-issue
- Treat unexpected 30202 spikes as a security incident
When it happens
Trigger: Parsing a token with a jwt-secret-key different from the one used to sign it; also triggered by algorithm-keytype mismatch (e.g. token signed RS256 but secret configured for HMAC-style verification).
Common situations: Secret rotated or differs between the issuing service and the verifying service; different jwt-secret-key per environment (dev token sent to prod); typo in the secret; secret env var unset so a default/fallback value is used; using asymmetric keys without configuring key-type properly.
Related errors
- 请配置 jwt 秘钥
- 12002
- UsernameAndPassword 不能为空
- 未配置全局 Http Digest 认证参数
- 全局 Http Digest 认证参数配置错误,格式应如:username:password
AI-assisted analysis of dromara/Sa-Token@ac2c7f6e94 (2026-08-14).
Data as JSON: /api/errors/3010911bd43380da.
Report an issue: GitHub.