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
- 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).
- Confirm the front-end sends the field as 'code' in the login payload matching exactly what checkCode reads (loginData.get('code')).
- 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.
- Increase server.servlet.session.timeout and verify the captcha isn't expiring faster than the user can submit.
- 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.
- 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
- Always call /verifyCode on the same session immediately before login, with withCredentials on.
- Refresh the captcha image and re-fetch /verifyCode whenever you show a new login attempt.
- Use a shared session store (Spring Session + Redis) or sticky sessions behind a load balancer so the verify_code survives across nodes.
- Stop swallowing the IOException in the JSON branch — log or rethrow it so captcha failures caused by parse errors are diagnosable.
- Add a test that posts a correct code (success) and a wrong code (assert 401 with the captcha message).
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.