java实现短信验证码
短信验证码是项目登录、注册、找回密码、手机号绑定最核心的功能几乎所有移动端、后台系统必备。很多新手只实现了「发验证码校验」但缺少防刷、过期、限流、幂等、安全校验上线极易被刷爆短信费、产生安全漏洞。本文给大家一套可直接上线的 Java 短信验证码完整方案适配 SpringBoot Redis包含核心流程、简易版Session、生产版Redis分布式、阿里云短信对接、防刷限流、异常处理、生产避坑完全对标企业级项目规范。一、整体业务流程标准企业流程完整短信验证码分为两大接口发送验证码接口、验证码登录/校验接口标准执行链路如下1. 发送验证码流程接收前端手机号正则校验手机号格式防刷校验判断该手机号/IP 是否频繁发送1分钟1次、1小时5次生成 4/6 位随机数字验证码将「手机号-验证码」存入 Redis设置5分钟过期调用第三方短信 API阿里云/腾讯云下发短信记录发送次数用于限流防刷2. 验证码登录/校验流程接收手机号 验证码校验 Redis 中是否存在该手机号的验证码比对验证码是否一致校验成功立即删除验证码防止重复使用执行登录/注册/绑定逻辑下发 Token二、两种实现方案对比1. Session 方案单机、老旧项目将验证码存在服务端 Session 中。2. Redis 方案生产、推荐、分布式验证码缓存统一放 Redis全局共享、自带过期、支持集群。三、环境依赖准备基于 SpringBoot 项目需要 Redis Hutool 工具随机数、正则工具。!-- Redis -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency !-- Hutool 工具类强烈推荐 -- dependency groupIdcn.hutool/groupId artifactIdhutool-all/artifactId version5.8.22/version /dependency四、常量统一配置新建验证码常量类统一 Key、过期时间、防刷时间方便后期维护。public class SmsConstant { // 验证码Redis key前缀 public static final String SMS_CODE_KEY sms:code:; // 验证码冷却key防刷 public static final String SMS_LIMIT_KEY sms:limit:; // 验证码有效期 5分钟 public static final int CODE_EXPIRE_SECOND 5 * 60; // 发送冷却时间 60秒1分钟只能发一次 public static final int LIMIT_EXPIRE_SECOND 60; }五、核心工具手机号正则校验必须校验手机号格式避免无效号码浪费短信费用。public static boolean isPhoneInvalid(String phone){ if(StrUtil.isBlank(phone)){ return true; } // 国内手机号正则 return !phone.matches(^1[3-9]\\d{9}$); }六、生产级发送验证码完整实现核心亮点格式校验 60秒防刷 随机6位验证码 过期自动失效。RestController RequestMapping(/sms) public class SmsController { Autowired private StringRedisTemplate stringRedisTemplate; // 发送验证码 GetMapping(/sendCode) public Result sendCode(String phone){ // 1. 校验手机号格式 if(isPhoneInvalid(phone)){ return Result.fail(手机号格式错误); } // 2. 防刷60秒内不能重复发送 String limitKey SmsConstant.SMS_LIMIT_KEY phone; Boolean hasLimit stringRedisTemplate.hasKey(limitKey); if(Boolean.TRUE.equals(hasLimit)){ return Result.fail(操作频繁请稍后再试); } // 3. 生成6位数字验证码 String code RandomUtil.randomNumbers(6); // 4. 存入Redis验证码5分钟过期 String codeKey SmsConstant.SMS_CODE_KEY phone; stringRedisTemplate.opsForValue().set(codeKey, code, SmsConstant.CODE_EXPIRE_SECOND, TimeUnit.SECONDS); // 5. 存入限流标记60秒冷却 stringRedisTemplate.opsForValue().set(limitKey, 1, SmsConstant.LIMIT_EXPIRE_SECOND, TimeUnit.SECONDS); // 6. 调用短信发送工具下面给出阿里云实现 SmsUtil.sendSms(phone, code); return Result.ok(发送成功); } }七、验证码校验登录接口校验成功立即删除验证码杜绝复用验证码漏洞。PostMapping(/login) public Result login(String phone, String code){ // 1. 校验手机号 if(isPhoneInvalid(phone)){ return Result.fail(手机号格式错误); } // 2. 获取缓存验证码 String key SmsConstant.SMS_CODE_KEY phone; String cacheCode stringRedisTemplate.opsForValue().get(key); // 3. 判断验证码 if(StrUtil.isBlank(cacheCode) || !cacheCode.equals(code)){ return Result.fail(验证码错误或已过期); } // 4. 验证成功立即删除验证码核心安全步骤 stringRedisTemplate.delete(key); // 5. 后续登录逻辑查询用户、注册用户、生成Token // ...业务代码省略 return Result.ok(登录成功); }八、阿里云短信工具类企业开发最常用阿里云短信服务封装工具类替换自己的 AccessKey、模板即可。public class SmsUtil { // 替换为自己的阿里云参数 private static final String ACCESS_KEY_ID xxx; private static final String ACCESS_KEY_SECRET xxx; private static final String SIGN_NAME xxx; private static final String TEMPLATE_CODE SMS_xxxx; public static void sendSms(String phone, String code){ // 初始化客户端 DefaultProfile profile DefaultProfile.getProfile(cn-hangzhou, ACCESS_KEY_ID, ACCESS_KEY_SECRET); IAcsClient client new DefaultAcsClient(profile); // 设置模板参数 SendSmsRequest request new SendSmsRequest(); request.setPhoneNumbers(phone); request.setSignName(SIGN_NAME); request.setTemplateCode(TEMPLATE_CODE); request.setTemplateParam({\code\:\code\}); try { client.getAcsResponse(request); } catch (Exception e) { log.error(短信发送失败,e); throw new RuntimeException(短信发送失败); } } }Maven 阿里云短信依赖dependency groupIdcom.aliyun/groupId artifactIdaliyun-java-sdk-core/artifactId version4.5.16/version /dependency dependency groupIdcom.aliyun/groupId artifactIdaliyun-java-sdk-dysmsapi/artifactId version2.1.0/version /dependency九、Session 简易版实现适合新手理解原理禁止生产使用。GetMapping(/sendCodeBySession) public Result sendCodeBySession(String phone, HttpSession session){ if(isPhoneInvalid(phone)){ return Result.fail(手机号格式错误); } String code RandomUtil.randomNumbers(6); session.setAttribute(sms_code,code); session.setAttribute(phone,phone); // 发送短信 SmsUtil.sendSms(phone,code); return Result.ok(); }十、生产必须加的安全优化1. 防刷策略防薅羊毛、刷短信单手机号60秒1次单日单手机号最多 10 次单IP限流防止批量脚本轰炸2. 验证码安全策略验证码校验成功立即删除不可重复使用固定 5 分钟过期避免永久有效漏洞后端生成验证码前端绝对不能生成3. 异常防护短信发送失败不扣次数、不存入有效验证码全局异常捕获避免报错暴露接口细节日志记录手机号、发送结果方便排查问题十一、常见Bug与坑点总结集群部署验证码失效用了 Session 存储未使用 Redis验证码可重复使用校验成功没有 delete key短信被刷爆扣费没有做 60s 防刷限流手机号格式不校验空号、错误号码浪费短信费用验证码长期有效未设置过期时间存在劫持风险十二、全文总结Java 短信验证码的企业级标准答案SpringBoot Redis 缓存验证码 时间过期 发送防刷限流 校验销毁验证码 第三方短信平台。不要只做基础功能安全、防刷、过期、销毁才是上线的关键。本文代码全部可直接复制运行适配所有 Java 后台、移动端、小程序项目。