防止手机验证码被刷是开发中常见的安全问题,尤其是在涉及用户注册、登录或敏感操作时。攻击者可能会通过自动化脚本频繁请求验证码,导致短信轰炸或资源浪费。以下是如何在 React + TypeScript 前端和 Node.js + Express 后端中防止验证码被刷的深度分析,并结合代码实现。
1. 常见的攻击场景
短信轰炸:
- 攻击者通过脚本频繁请求验证码,导致用户手机收到大量短信。
验证码滥用:
- 攻击者通过暴力破解或重放攻击,尝试破解验证码。
资源耗尽:
- 频繁请求验证码可能导致服务器资源耗尽,影响正常用户。
2. 防御措施
2.1 前端防御措施
增加图形验证码:
- 在发送短信验证码之前,要求用户输入图形验证码,防止自动化脚本。
请求频率限制:
- 在前端限制用户点击“发送验证码”按钮的频率(如 60 秒内只能点击一次)。
IP 和设备指纹识别:
- 记录用户 IP 和设备信息,用于后端分析和限制。
2.2 后端防御措施
请求频率限制:
- 使用 Redis 或内存缓存记录每个手机号或 IP 的请求频率,限制单位时间内的请求次数。
验证码有效期:
- 设置验证码的较短有效期(如 5 分钟),减少被滥用的风险。
验证码使用次数限制:
- 每个验证码只能使用一次,使用后立即失效。
黑名单机制:
- 将频繁请求验证码的 IP 或手机号加入黑名单,暂时禁止其请求。
日志监控:
- 记录验证码请求日志,监控异常行为。
3. 前端实现(React + TypeScript)
3.1 增加图形验证码
在发送短信验证码之前,要求用户输入图形验证码。
import React, { useState } from 'react';
import axios from 'axios';
const SmsVerification: React.FC = () => {
const [phone, setPhone] = useState('');
const [captcha, setCaptcha] = useState('');
const [isLoading, setIsLoading] = useState(false);
const handleSendCode = async () => {
if (!captcha) {
alert('请输入图形验证码');
return;
}
setIsLoading(true);
try {
await axios.post('/api/send-sms', { phone, captcha });
alert('验证码已发送');
} catch (error) {
alert('发送验证码失败');
} finally {
setIsLoading(false);
}
};
return (
<div>
<input
type="text"
placeholder="请输入手机号"
value={phone}
onChange={(e) => setPhone(e.target.value)}
/>
<input
type="text"
placeholder="请输入图形验证码"
value={captcha}
onChange={(e) => setCaptcha(e.target.value)}
/>
<button onClick={handleSendCode} disabled={isLoading}>
{isLoading ? '发送中...' : '发送验证码'}
</button>
</div>
);
};
export default SmsVerification;
3.2 请求频率限制
在前端限制用户点击“发送验证码”按钮的频率。
import React, { useState, useEffect } from 'react';
const SmsVerification: React.FC = () => {
const [countdown, setCountdown] = useState(0);
const handleSendCode = () => {
// 设置倒计时 60 秒
setCountdown(60);
// 发送验证码逻辑...
};
useEffect(() => {
if (countdown > 0) {
const timer = setTimeout(() => setCountdown(countdown - 1), 1000);
return () => clearTimeout(timer);
}
}, [countdown]);
return (
<div>
<button onClick={handleSendCode} disabled={countdown > 0}>
{countdown > 0 ? `${countdown}秒后重试` : '发送验证码'}
</button>
</div>
);
};
export default SmsVerification;
4. 后端实现(Node.js + Express)
4.1 使用 Redis 限制请求频率
使用 Redis 记录每个手机号或 IP 的请求频率。
const express = require('express');
const redis = require('redis');
const app = express();
const client = redis.createClient();
app.use(express.json());
// 限制每分钟最多发送 1 次验证码
app.post('/api/send-sms', async (req, res) => {
const { phone, captcha } = req.body;
// 验证图形验证码
if (!validateCaptcha(captcha)) {
return res.status(400).json({ message: '图形验证码错误' });
}
const key = `sms:${phone}`;
const count = await client.getAsync(key);
if (count && parseInt(count) >= 1) {
return res.status(429).json({ message: '请求过于频繁,请稍后再试' });
}
// 发送短信验证码
const code = generateRandomCode();
await sendSms(phone, code);
// 记录请求次数
await client.setAsync(key, 1, 'EX', 60); // 60 秒内只能发送 1 次
res.json({ message: '验证码已发送' });
});
// 启动服务器
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
4.2 验证码有效期和使用次数限制
在 Redis 中存储验证码,并设置有效期和使用次数。
app.post('/api/verify-code', async (req, res) => {
const { phone, code } = req.body;
const key = `code:${phone}`;
const storedCode = await client.getAsync(key);
if (!storedCode || storedCode !== code) {
return res.status(400).json({ message: '验证码错误或已过期' });
}
// 验证成功后删除验证码
await client.delAsync(key);
res.json({ message: '验证成功' });
});
4.3 黑名单机制
将频繁请求验证码的 IP 或手机号加入黑名单。
app.post('/api/send-sms', async (req, res) => {
const { phone, captcha } = req.body;
const ip = req.ip;
// 检查黑名单
const isBlacklisted = await client.getAsync(`blacklist:${ip}`);
if (isBlacklisted) {
return res.status(403).json({ message: '您的 IP 已被限制' });
}
// 其他逻辑...
});
5. 总结
5.1 核心防御措施
- 图形验证码:防止自动化脚本。
- 请求频率限制:限制单位时间内的请求次数。
- 验证码有效期:设置较短的验证码有效期。
- 黑名单机制:限制恶意 IP 或手机号。
5.2 注意事项
- 用户体验:在保证安全的同时,尽量减少对正常用户的干扰。
- 日志监控:记录验证码请求日志,及时发现异常行为。
- 多维度限制:结合 IP、设备指纹、用户行为等多维度进行限制。
通过以上措施,可以有效防止手机验证码被刷,保障系统的安全性和稳定性。