{"record":{"id":"b1ecf741d713980f","repo":"wuyouzhuguli/SpringAll","slug":"error-b1ecf7","errorCode":null,"errorMessage":"验证码已过期！","messagePattern":"验证码已过期！","errorType":"validation","errorClass":"Exception","httpStatus":null,"severity":"warning","filePath":"65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/validate/smscode/SmsCodeFilter.java","lineNumber":52,"sourceCode":"            } catch (Exception e) {\n                authenticationFailureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, new AuthenticationServiceException(e.getMessage()));\n                return;\n            }\n        }\n        filterChain.doFilter(httpServletRequest, httpServletResponse);\n    }\n\n    private void validateCode(ServletWebRequest servletWebRequest) throws Exception {\n        String smsCodeInRequest = ServletRequestUtils.getStringParameter(servletWebRequest.getRequest(), \"smsCode\");\n        String mobileInRequest = ServletRequestUtils.getStringParameter(servletWebRequest.getRequest(), \"mobile\");\n\n        String codeInRedis = redisCodeService.get(servletWebRequest, mobileInRequest);\n\n        if (StringUtils.isBlank(smsCodeInRequest)) {\n            throw new Exception(\"验证码不能为空！\");\n        }\n        if (codeInRedis == null) {\n            throw new Exception(\"验证码已过期！\");\n        }\n        if (!StringUtils.equalsIgnoreCase(codeInRedis, smsCodeInRequest)) {\n            throw new Exception(\"验证码不正确！\");\n        }\n        redisCodeService.remove(servletWebRequest, mobileInRequest);\n\n    }\n}","sourceCodeStart":34,"sourceCodeEnd":60,"githubUrl":"https://github.com/wuyouzhuguli/SpringAll/blob/614d2578d9495acf53cc02f2dee9c6131cc5e51a/65.Spring-Security-OAuth2-Config/src/main/java/cc/mrbird/security/validate/smscode/SmsCodeFilter.java#L34-L60","documentation":"A plain java.lang.Exception '验证码已过期！' (verification code expired) is thrown by SmsCodeFilter.validateCode when redisCodeService.get(...) returns null for the given mobile. The SMS code is stored in Redis with a TTL keyed by mobile number; a null means no code exists under that key. This gate runs before the SMS authentication provider.","triggerScenarios":"The SMS code's Redis TTL elapsed before login was attempted; the code was already consumed by a successful prior validate (validateCode calls redisCodeService.remove after a match, so resubmission hits null); the code was never generated/sent for that mobile; or Redis was flushed/restarted losing the key.","commonSituations":"User waited longer than the configured code validity window (often 60–300s) before submitting; double-submit of the login form after a first success (remove-on-success deletes the key); the SMS-send endpoint and the validate endpoint key the code differently (different mobile normalization or key prefix); or the Redis connection/serialization changed so get returns null for a key that was written by a prior deploy.","solutions":["Request a fresh SMS code (re-trigger the send-code endpoint) and submit login within the configured TTL window.","Verify redisCodeService uses an identical key (same mobile normalization and prefix) for both set (on send) and get (on validate), and that the TTL on set is non-zero.","Confirm validateCode's remove() is only called after a successful match, so legitimate first attempts are not prematurely invalidated.","Check Redis connectivity and that keys survive across requests (no flush, correct DB index, persistent rather than in-memory config in tests).","Make the code's expiry window explicit to the user (countdown UI) so submissions land inside the TTL."],"exampleFix":"// before\nif (codeInRedis == null) {\n    throw new Exception(\"验证码已过期！\");\n}\n\n// after — typed exception + distinguish never-issued vs expired\nif (codeInRedis == null) {\n    throw new ValidateCodeException(\"验证码已过期或未发送，请重新获取\");\n}","handlingStrategy":"validation","validationCode":"// Check existence in Redis before delegating to the throwing filter\nboolean codeAlive = redisCodeService.get(new ServletWebRequest(request), mobile) != null;\nif (!codeAlive) {\n    return \"验证码已失效，请重新获取\";\n}","typeGuard":"private boolean codeStillValid(ServletWebRequest req, String mobile) {\n    return redisCodeService.get(req, mobile) != null;\n}","tryCatchPattern":"try {\n    filterChain.doFilter(request, response);\n} catch (Exception e) {\n    if (\"验证码已过期！\".equals(e.getMessage())) {\n        response.setStatus(400);\n        response.getWriter().write(\"验证码已过期，请重新获取\");\n    } else {\n        throw e;\n    }\n}","preventionTips":["Send the SMS code and submit login promptly within the configured TTL.","Use an identical Redis key (same mobile normalization and prefix) for set and get; verify with a KEYS/SCAN in dev.","Only remove the code after a successful match — never before — to avoid premature invalidation.","Confirm Redis persistence/connectivity so codes are not lost between requests in tests."],"tags":["spring-security","sms-code","redis","ttl","filter"],"backgroundTag":null,"analyzedSha":"614d2578d9495acf53cc02f2dee9c6131cc5e51a","analyzedAt":"2026-08-14T04:40:03.488Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}