{"record":{"id":"0a82a17eafa342e4","repo":"paascloud/paascloud-master","slug":"ip","errorCode":null,"errorMessage":"IP当天短信发送数上限","messagePattern":"IP当天短信发送数上限","errorType":"error_code","errorClass":"ValidateCodeException","httpStatus":null,"severity":"warning","filePath":"paascloud-common/paascloud-security-core/src/main/java/com/paascloud/security/core/validate/code/sms/SmsCodeProcessor.java","lineNumber":123,"sourceCode":"\t\tInteger sendSmsRateCount = (Integer) redisTemplate.opsForValue().get(sendSmsRateKey);\n\t\tif (sendSmsRateCount != null) {\n\t\t\tlog.error(\"操作频率过快 ipAddr={}, mobile={}\", ipAddr, mobile);\n\t\t\tthrow new ValidateCodeException(\"操作频率过快\");\n\t\t} else {\n\t\t\tredisTemplate.opsForValue().set(sendSmsRateKey, 1, 1, TimeUnit.MINUTES);\n\t\t}\n\n\t\tInteger mobileSmsCount = (Integer) redisTemplate.opsForValue().get(mobileSmsCountKey);\n\t\tif (mobileSmsCount != null && mobileSmsCount > sms.getMobileMaxSendCount()) {\n\t\t\tlog.error(\"Mobile当天短信发送数上限 ipAddr={}, mobile={}\", ipAddr, mobile);\n\t\t\tthrow new ValidateCodeException(\"Mobile当天短信发送数上限\");\n\t\t} else {\n\t\t\tredisTemplate.opsForValue().set(mobileSmsCountKey, mobileSmsCount == null ? 1 : mobileSmsCount + 1, 1, TimeUnit.DAYS);\n\t\t}\n\t\tInteger ipSmsCount = (Integer) redisTemplate.opsForValue().get(ipSmsCountKey);\n\t\tif (ipSmsCount != null && ipSmsCount > sms.getIpMaxSendCount()) {\n\t\t\tlog.error(\"IP当天短信发送数上限 ipAddr={}, mobile={}\", ipAddr, mobile);\n\t\t\tthrow new ValidateCodeException(\"IP当天短信发送数上限\");\n\t\t} else {\n\t\t\tredisTemplate.opsForValue().set(ipSmsCountKey, ipSmsCount == null ? 1 : ipSmsCount + 1, 1, TimeUnit.DAYS);\n\t\t}\n\t\tInteger totalSmsCount = (Integer) redisTemplate.opsForValue().get(totalSmsCountKey);\n\t\tif (totalSmsCount != null && totalSmsCount > sms.getTotalMaxSendCount()) {\n\t\t\tlog.error(\"当天短信发送数上限 ipAddr={}, mobile={}\", ipAddr, mobile);\n\t\t\tthrow new ValidateCodeException(\"当天短信发送数上限\");\n\t\t} else {\n\t\t\tredisTemplate.opsForValue().set(totalSmsCountKey, totalSmsCount == null ? 1 : totalSmsCount + 1, 1, TimeUnit.DAYS);\n\t\t}\n\t}\n\n\tprivate String getRemoteAddr(HttpServletRequest request) {\n\t\tString ipAddress = request.getHeader(X_FORWARDED_FOR);\n\t\tif (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) {\n\t\t\tipAddress = request.getHeader(PROXY_CLIENT_IP);\n\t\t}\n\t\tif (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) {","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/paascloud/paascloud-master/blob/781281a9503332ed3cef44ea618349d14230a127/paascloud-common/paascloud-security-core/src/main/java/com/paascloud/security/core/validate/code/sms/SmsCodeProcessor.java#L105-L141","documentation":"checkSendSmsCount also enforces a per-source-IP daily SMS quota: it reads ipSmsCountKey from Redis and throws ValidateCodeException('IP当天短信发送数上限') when the count exceeds sms.getIpMaxSendCount(). This blocks a single client IP from requesting too many SMS codes in one day, defending against bulk abuse where attackers rotate phone numbers but share an IP.","triggerScenarios":"SMS code requests (send -> checkSendSmsCount) originating from one IP address whose Redis day-counter exceeds smsProperties.getIpMaxSendCount(); the counter is incremented on each send and kept for 1 day.","commonSituations":"Office/CGNAT networks where many users share one public IP and collectively exhaust the IP quota; load/perf testing from a single machine; ipMaxSendCount configured too low; users behind proxies or corporate NAT hitting the cap.","solutions":["Raise sms.getIpMaxSendCount() in the SMS code properties if the limit is unrealistically low for shared-IP networks","Delete the Redis ipSmsCountKey to reset the quota (testing only)","If deployment sits behind an LB/proxy, ensure getRemoteAddr() reads X-Forwarded-For so counts are per real client, not per proxy IP","Surface a clear message to the user ('too many requests from your network') when catching ValidateCodeException"],"exampleFix":"// before: proxy IP counted, all users share one quota\nString remoteAddr = request.getRemoteAddr();\n// after: resolve real client IP behind reverse proxy\nString remoteAddr = request.getHeader(\"X-Forwarded-For\");\nif (PublicUtil.isNotEmpty(remoteAddr)) {\n    remoteAddr = remoteAddr.split(\",\")[0].trim();\n}","handlingStrategy":"try-catch","validationCode":"Integer ipCount = (Integer) redisTemplate.opsForValue().get(ipSmsCountKey);\nif (ipCount != null && ipCount > maxIpSendCount) {\n    throw new BizException(\"SMS daily limit reached for your network\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    smsCodeSender.send(mobile);\n} catch (ValidateCodeException e) {\n    log.warn(\"IP SMS quota hit: {}\", e.getMessage());\n    return Result.error(429, \"当前网络请求过于频繁，请稍后再试\");\n}","preventionTips":["Behind a load balancer, make sure the real client IP (X-Forwarded-For) is used so shared proxy IPs don't pool quota","Set ipMaxSendCount high enough for CGNAT/office networks where many users share one IP","Monitor ipSmsCount keys for abnormal growth indicating scraping or abuse","Combine with the per-mobile limit so a single abuser is stopped earlier by the tighter quota"],"tags":["rate-limit","sms","redis","ip-throttling"],"backgroundTag":"rate-limit-exceeded","analyzedSha":"781281a9503332ed3cef44ea618349d14230a127","analyzedAt":"2026-09-10T10:59:02.070Z","contentChangedAt":"2026-09-10T10:59:02.070Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}