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> <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 后台、移动端、小程序项目。
更多推荐
所有评论(0)