lenve/vhr · warning · AuthenticationServiceException

验证码不正确

Error message

验证码不正确

What it means

Thrown by LoginFilter.checkCode when the captcha supplied by the client does not match the server-side verify_code stored in the session. The comparison is case-insensitive against the value stored under session attribute 'verify_code' (set by the captcha-generation endpoint). It fires when the client code is null/blank, when no verify_code was stored (session lost), or when the two strings differ — a deliberate early rejection before username/password are even checked.

Source

Thrown at vhr/vhrserver/vhr-web/src/main/java/org/javaboy/vhr/config/LoginFilter.java:72

            }
            username = username.trim();
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
                    username, password);
            setDetails(request, authRequest);
            Hr principal = new Hr();
            principal.setUsername(username);
            sessionRegistry.registerNewSession(request.getSession(true).getId(), principal);
            return this.getAuthenticationManager().authenticate(authRequest);
        } else {
            checkCode(response, request.getParameter("code"), verify_code);
            return super.attemptAuthentication(request, response);
        }
    }

    public void checkCode(HttpServletResponse resp, String code, String verify_code) {
        if (code == null || verify_code == null || "".equals(code) || !verify_code.toLowerCase().equals(code.toLowerCase())) {
            //验证码不正确
            throw new AuthenticationServiceException("验证码不正确");
        }
    }
}

View on GitHub (pinned to 03abbd35af)

Solutions

  1. Ensure the captcha-generation endpoint (/verifyCode) is called and completes — setting the verify_code session attribute — before the login POST, on the same session (same JSESSIONID cookie).
  2. Confirm the front-end sends the field as 'code' in the login payload matching exactly what checkCode reads (loginData.get('code')).
  3. If running multiple instances, configure a shared session store (Spring Session + Redis) so the captcha written on one node is readable on another, or enable sticky sessions.
  4. Increase server.servlet.session.timeout and verify the captcha isn't expiring faster than the user can submit.
  5. Fix the swallowed IOException in the JSON branch: currently a body-parse failure silently leaves loginData empty and then checkCode fails opaquely — log or rethrow so the real cause surfaces.
  6. If the captcha is cosmetic in dev, temporarily disable the checkCode call in the JSON branch, but never ship that.

Example fix

// Front-end: fetch captcha on the SAME session, then post with matching field name
// before
axios.post('/doLogin', { username, password }); // no code, verify_code unset -> fail
// after
await axios.get('/verifyCode', { responseType: 'blob', withCredentials: true });
await axios.post('/doLogin', { username, password, code }, { withCredentials: true });
Defensive patterns

Strategy: validation

Validate before calling

// Front-end: make sure a code is present and the captcha session is established first.
async function login(payload) {
  // 1) prime the session + verify_code attribute
  await axios.get('/verifyCode', { responseType: 'blob', withCredentials: true });
  // 2) require a non-empty code client-side before posting
  if (!payload.code || !payload.code.trim()) {
    throw new Error('请输入验证码');
  }
  return axios.post('/doLogin', payload, { withCredentials: true });
}

Type guard

// Java: simple presence guard mirroring checkCode so callers can validate early.
boolean captchaValid(String code, String verifyCode) {
    return code != null && verifyCode != null && !code.isEmpty()
        && verifyCode.equalsIgnoreCase(code);
}
// usage before authenticate(): if (!captchaValid(code, verify_code)) throw ...;

Try / catch

// In the AuthenticationFailureHandler, surface a friendly message for this case:
@Override
public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp,
                                    AuthenticationException ex) throws IOException {
    String msg = (ex instanceof AuthenticationServiceException
                  && ex.getMessage().contains("验证码"))
        ? "验证码不正确,请刷新后重试" : ex.getMessage();
    resp.setStatus(401);
    resp.setContentType("application/json;charset=UTF-8");
    resp.getWriter().write(new ObjectMapper().writeValueAsString(Map.of("status", 401, "msg", msg)));
}
// Also fix the swallowed IOException in the JSON branch so a parse error stops masking the real cause.

Prevention

When it happens

Trigger: Login POST body field 'code' is missing, empty, or spelled differently; the verify_code session attribute is null because the captcha endpoint was never called or returned before login; the session used to store the captcha differs from the session on the login request (different JSESSIONID cookie); or the user simply typed the wrong captcha. checkCode is also called in the finally block of the JSON-login path, so any body-parse IOException that leaves loginData empty will still trigger it.

Common situations: Front-end skipped the /verifyCode call or did not wait for it to set the session cookie before posting login; the captcha image was refreshed in the UI but the request reused the old session/old code; the session timed out between loading the captcha and submitting; container restart cleared in-memory sessions; a load balancer routed the captcha request and the login request to different nodes without session affinity/Redis; the JSON parse failed silently (caught and swallowed IOException) leaving 'code' null.

Related errors


AI-assisted analysis of lenve/vhr@03abbd35af (2026-08-14). Data as JSON: /api/errors/2a47de7ad3c1e8e1. Report an issue: GitHub.