dromara/Sa-Token · error · RuntimeException
验证码错误
Error message
验证码错误
What it means
Thrown by PhoneCodeUtil.checkCode when the SMS verification code submitted by the user does not match the code stored in SaTokenDao under key 'phone_code:<phone>'. Codes are generated as a 6-digit random number and stored with a 5-minute (300s) TTL, then deleted immediately after one successful verification. A mismatch — including an expired (null) or already-consumed code — produces this RuntimeException.
Source
Thrown at sa-token-demo/sa-token-demo-device-lock/src/main/java/com/pj/util/PhoneCodeUtil.java:25
* 手机验证码工具类 (仅做逻辑模拟,不做真实发送)
*
* @author click33
* @since 2024/8/23
*/
public class PhoneCodeUtil {
// 指定手机号发送验证码
public static void sendCode(String phone) {
String code = SaFoxUtil.getRandomNumber(100000, 999999) + "";
SaManager.getSaTokenDao().set("phone_code:" + phone, code, 60 * 5);
System.out.println("手机号:" + phone + ",验证码:" + code + ",已发送成功");
}
// 校验验证码是否正确,不正确则抛出异常
public static void checkCode(String phone, String code) {
String oldCode = SaManager.getSaTokenDao().get("phone_code:" + phone);
if( ! code.equals(oldCode) ) {
throw new RuntimeException("验证码错误");
}
// 验证通过后,立即删除验证码
SaManager.getSaTokenDao().delete("phone_code:" + phone);
}
}View on GitHub (pinned to ac2c7f6e94)
Solutions
- Re-send a fresh code via PhoneCodeUtil.sendCode(phone) and have the user enter the newest SMS within 5 minutes.
- If codes seem valid but always fail, verify both sendCode and checkCode hit the same SaTokenDao instance (e.g. both go to Redis, not one to memory after a restart).
- Treat a null stored code distinctly (expired/never sent) instead of reporting 'wrong code', to guide the user to resend.
- For production, replace the demo's RuntimeException with a typed exception and rate-limit sendCode to prevent abuse.
Example fix
// before
public static void checkCode(String phone, String code) {
String oldCode = SaManager.getSaTokenDao().get("phone_code:" + phone);
if( ! code.equals(oldCode) ) {
throw new RuntimeException("验证码错误");
}
SaManager.getSaTokenDao().delete("phone_code:" + phone);
}
// after
public static void checkCode(String phone, String code) {
String oldCode = SaManager.getSaTokenDao().get("phone_code:" + phone);
if (oldCode == null) {
throw new RuntimeException("验证码已过期或未发送,请重新获取");
}
if (!oldCode.equals(code)) {
throw new RuntimeException("验证码错误");
}
SaManager.getSaTokenDao().delete("phone_code:" + phone);
} Defensive patterns
Strategy: validation
Validate before calling
String stored = SaManager.getSaTokenDao().get("phone_code:" + phone);
if (stored == null) {
// expired or never sent; do not call checkCode
return "code expired, please resend";
}
if (!stored.equals(inputCode)) {
return "wrong code";
}
PhoneCodeUtil.checkCode(phone, inputCode); Try / catch
try { PhoneCodeUtil.checkCode(phone, code); } catch (RuntimeException e) { if ("验证码错误".equals(e.getMessage())) { /* prompt resend */ } else throw e; } Prevention
- Always call sendCode immediately before prompting the user, and document the 5-minute TTL in the UI.
- Invalidate/resend rather than reuse after a failed attempt; the stored code is only deleted on success.
- Ensure the same SaTokenDao backend (e.g. Redis) serves both send and check across app restarts.
When it happens
Trigger: Calling checkCode(phone, code) where the submitted code differs from SaManager.getSaTokenDao().get("phone_code:" + phone). This happens when: user typos the code; more than 300s elapsed since sendCode() (DAO returns null, and code.equals(null-String) is false); the code was already verified once (deleted after success); or sendCode() was called twice and the user reads the first SMS.
Common situations: Demo device-lock login flows where the user waits too long on the input page, resends the code but enters the older SMS, or retries login after a successful attempt. Also when the underlying SaTokenDao is a no-op/in-memory dao and the app restarted between send and check.
Related errors
AI-assisted analysis of dromara/Sa-Token@ac2c7f6e94 (2026-08-14).
Data as JSON: /api/errors/e4b3af46eea6e339.
Report an issue: GitHub.