{"record":{"id":"2a47de7ad3c1e8e1","repo":"lenve/vhr","slug":"error-2a47de","errorCode":null,"errorMessage":"验证码不正确","messagePattern":"验证码不正确","errorType":"exception","errorClass":"AuthenticationServiceException","httpStatus":null,"severity":"warning","filePath":"vhr/vhrserver/vhr-web/src/main/java/org/javaboy/vhr/config/LoginFilter.java","lineNumber":72,"sourceCode":"            }\n            username = username.trim();\n            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(\n                    username, password);\n            setDetails(request, authRequest);\n            Hr principal = new Hr();\n            principal.setUsername(username);\n            sessionRegistry.registerNewSession(request.getSession(true).getId(), principal);\n            return this.getAuthenticationManager().authenticate(authRequest);\n        } else {\n            checkCode(response, request.getParameter(\"code\"), verify_code);\n            return super.attemptAuthentication(request, response);\n        }\n    }\n\n    public void checkCode(HttpServletResponse resp, String code, String verify_code) {\n        if (code == null || verify_code == null || \"\".equals(code) || !verify_code.toLowerCase().equals(code.toLowerCase())) {\n            //验证码不正确\n            throw new AuthenticationServiceException(\"验证码不正确\");\n        }\n    }\n}\n","sourceCodeStart":54,"sourceCodeEnd":76,"githubUrl":"https://github.com/lenve/vhr/blob/03abbd35af24e55368ce4e09f4038dc2aba3ff5f/vhr/vhrserver/vhr-web/src/main/java/org/javaboy/vhr/config/LoginFilter.java#L54-L76","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// Front-end: fetch captcha on the SAME session, then post with matching field name\n// before\naxios.post('/doLogin', { username, password }); // no code, verify_code unset -> fail\n// after\nawait axios.get('/verifyCode', { responseType: 'blob', withCredentials: true });\nawait axios.post('/doLogin', { username, password, code }, { withCredentials: true });","handlingStrategy":"validation","validationCode":"// Front-end: make sure a code is present and the captcha session is established first.\nasync function login(payload) {\n  // 1) prime the session + verify_code attribute\n  await axios.get('/verifyCode', { responseType: 'blob', withCredentials: true });\n  // 2) require a non-empty code client-side before posting\n  if (!payload.code || !payload.code.trim()) {\n    throw new Error('请输入验证码');\n  }\n  return axios.post('/doLogin', payload, { withCredentials: true });\n}","typeGuard":"// Java: simple presence guard mirroring checkCode so callers can validate early.\nboolean captchaValid(String code, String verifyCode) {\n    return code != null && verifyCode != null && !code.isEmpty()\n        && verifyCode.equalsIgnoreCase(code);\n}\n// usage before authenticate(): if (!captchaValid(code, verify_code)) throw ...;","tryCatchPattern":"// In the AuthenticationFailureHandler, surface a friendly message for this case:\n@Override\npublic void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp,\n                                    AuthenticationException ex) throws IOException {\n    String msg = (ex instanceof AuthenticationServiceException\n                  && ex.getMessage().contains(\"验证码\"))\n        ? \"验证码不正确，请刷新后重试\" : ex.getMessage();\n    resp.setStatus(401);\n    resp.setContentType(\"application/json;charset=UTF-8\");\n    resp.getWriter().write(new ObjectMapper().writeValueAsString(Map.of(\"status\", 401, \"msg\", msg)));\n}\n// Also fix the swallowed IOException in the JSON branch so a parse error stops masking the real cause.","preventionTips":["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)."],"tags":["spring-security","authentication","captcha","session","login"],"backgroundTag":null,"analyzedSha":"03abbd35af24e55368ce4e09f4038dc2aba3ff5f","analyzedAt":"2026-08-14T04:43:34.269Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}