短信验证码是项目登录、注册、找回密码、手机号绑定最核心的功能,几乎所有移动端、后台系统必备。

很多新手只实现了「发验证码+校验」,但缺少防刷、过期、限流、幂等、安全校验,上线极易被刷爆短信费、产生安全漏洞。

本文给大家一套可直接上线的 Java 短信验证码完整方案,适配 SpringBoot + Redis,包含:核心流程、简易版(Session)、生产版(Redis分布式)、阿里云短信对接、防刷限流、异常处理、生产避坑,完全对标企业级项目规范。

一、整体业务流程(标准企业流程)

完整短信验证码分为两大接口:发送验证码接口验证码登录/校验接口,标准执行链路如下:

1. 发送验证码流程

  1. 接收前端手机号,正则校验手机号格式

  2. 防刷校验:判断该手机号/IP 是否频繁发送(1分钟1次、1小时5次)

  3. 生成 4/6 位随机数字验证码

  4. 将「手机号-验证码」存入 Redis,设置5分钟过期

  5. 调用第三方短信 API(阿里云/腾讯云)下发短信

  6. 记录发送次数,用于限流防刷

2. 验证码登录/校验流程

  1. 接收手机号 + 验证码

  2. 校验 Redis 中是否存在该手机号的验证码

  3. 比对验证码是否一致

  4. 校验成功立即删除验证码(防止重复使用)

  5. 执行登录/注册/绑定逻辑,下发 Token

二、两种实现方案对比

1. Session 方案(单机、老旧项目)

将验证码存在服务端 Session 中。

2. Redis 方案(生产、推荐、分布式)

验证码缓存统一放 Redis,全局共享、自带过期、支持集群。

三、环境依赖准备

基于 SpringBoot 项目,需要 Redis + Hutool 工具(随机数、正则工具)。

<!-- Redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- Hutool 工具类(强烈推荐) --> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.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> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-core</artifactId> <version>4.5.16</version> </dependency> <dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-dysmsapi</artifactId> <version>2.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 后台、移动端、小程序项目。

更多推荐