SpringBoot实战:阿里云短信验证码集成与Redis防刷策略
短信验证码作为现代应用的身份验证基石,其稳定性和安全性直接影响用户体验和系统可靠性。对于Java开发者而言,如何在SpringBoot项目中快速集成阿里云短信服务,同时规避恶意刷取风险,是每个上线项目必须跨越的技术门槛。
1. 阿里云短信服务基础配置
在开始编码前,我们需要完成阿里云侧的准备工作。登录阿里云控制台,进入「短信服务」模块,这里需要重点关注三个核心配置项:
- 签名申请:选择签名类型为「验证码」,填写签名来源(如企业官网或APP名称),上传对应的资质证明文件
- 模板创建:新建「验证码」类型模板,内容示例:
您的验证码为${code},5分钟内有效 - 权限配置:为操作账号开通
AliyunDysmsFullAccess权限策略
注意:正式环境签名审核通常需要1-2个工作日,建议在项目初期就提前准备
关键参数获取位置:
| 参数类型 | 获取路径 | 示例 |
|---|---|---|
| AccessKey | 控制台 > 访问控制 > 用户管理 | LTAI5txxxxxxxxxx |
| 签名名称 | 短信服务 > 签名管理 | 阿里云验证 |
| 模板CODE | 短信服务 > 模板管理 | SMS_154950909 |
2. SpringBoot工程初始化
创建标准的SpringBoot项目后,首先配置必要的依赖项。除了基础的web starter,还需要添加:
<!-- 阿里云SDK核心库 --> <dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-core</artifactId> <version>4.5.16</version> </dependency> <!-- 短信服务专用SDK --> <dependency> <groupId>com.aliyun</groupId> <artifactId>dysmsapi20170525</artifactId> <version>2.0.23</version> </dependency> <!-- Redis集成 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>在application.yml中配置关键参数:
aliyun: sms: access-key-id: ${ALIYUN_ACCESS_KEY} access-key-secret: ${ALIYUN_ACCESS_SECRET} sign-name: 阿里云验证 template-code: SMS_154950909 endpoint: dysmsapi.aliyuncs.com spring: redis: host: localhost port: 6379 timeout: 30003. 验证码服务层实现
验证码生成建议采用线程安全的ThreadLocalRandom替代传统Random类:
public class VerificationCodeUtil { private static final String NUMBERS = "0123456789"; public static String generate(int length) { StringBuilder sb = new StringBuilder(length); for (int i = 0; i < length; i++) { int index = ThreadLocalRandom.current().nextInt(NUMBERS.length()); sb.append(NUMBERS.charAt(index)); } return sb.toString(); } }短信发送服务实现要点:
@Service @RequiredArgsConstructor public class SmsService { private final AliyunSmsConfig smsConfig; public SendSmsResponse sendVerificationCode(String phone, String code) throws Exception { Config config = new Config() .setAccessKeyId(smsConfig.getAccessKeyId()) .setAccessKeySecret(smsConfig.getAccessKeySecret()); config.endpoint = smsConfig.getEndpoint(); Client client = new Client(config); SendSmsRequest request = new SendSmsRequest() .setPhoneNumbers(phone) .setSignName(smsConfig.getSignName()) .setTemplateCode(smsConfig.getTemplateCode()) .setTemplateParam("{\"code\":\"" + code + "\"}"); return client.sendSms(request); } }4. Redis防刷策略深度优化
基础防刷方案存在三个潜在漏洞:
- 未限制单位时间发送次数
- 未校验验证码有效性
- 缺乏IP维度限制
改进后的Redis存储结构设计:
// 验证码存储Key设计 String codeKey = "sms:code:" + phone; // 发送次数计数Key设计 String countKey = "sms:count:" + phone; // IP限制Key设计 String ipKey = "sms:ip:" + ipAddress; // 复合校验逻辑 public boolean allowSend(String phone, String ip) { // 单IP每日限制 Long ipCount = redisTemplate.opsForValue().increment(ipKey); if (ipCount == 1) { redisTemplate.expire(ipKey, 1, TimeUnit.DAYS); } if (ipCount > 50) { return false; } // 手机号频次控制 Long phoneCount = redisTemplate.opsForValue().increment(countKey); if (phoneCount == 1) { redisTemplate.expire(countKey, 1, TimeUnit.HOURS); } return phoneCount <= 5; }验证码校验服务示例:
public boolean verifyCode(String phone, String inputCode) { String storedCode = redisTemplate.opsForValue().get("sms:code:" + phone); if (storedCode == null || !storedCode.equals(inputCode)) { return false; } // 验证通过后立即删除缓存 redisTemplate.delete("sms:code:" + phone); return true; }5. 生产环境注意事项
性能优化:
- 使用连接池配置RedisTemplate
- 对短信发送接口实现异步处理
- 添加熔断机制(如Sentinel)防止短信服务不可用
安全增强:
// 图形验证码前置校验 @GetMapping("/sms/code") public Result requestSmsCode( @RequestParam String phone, @RequestParam String captcha, HttpServletRequest request) { String sessionCaptcha = (String) request.getSession() .getAttribute("captcha"); if (!captcha.equalsIgnoreCase(sessionCaptcha)) { return Result.fail("图形验证码错误"); } // ...后续短信发送逻辑 }监控建议:
- 通过阿里云云监控设置短信发送量告警
- 记录Redis防刷触发日志用于安全审计
- 定期统计各手机号验证码使用情况
在用户注册流程中,建议采用以下验证链:
- 前端图形验证码
- 短信验证码频率控制
- 最终验证码校验
- 注册成功后的风控检查