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

  1. Re-send a fresh code via PhoneCodeUtil.sendCode(phone) and have the user enter the newest SMS within 5 minutes.
  2. 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).
  3. Treat a null stored code distinctly (expired/never sent) instead of reporting 'wrong code', to guide the user to resend.
  4. 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

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.