防止手机验证码被刷:React + TypeScript 与 Node.js + Express 的全面防御策略

防止手机验证码被刷是开发中常见的安全问题,尤其是在涉及用户注册、登录或敏感操作时。攻击者可能会通过自动化脚本频繁请求验证码,导致短信轰炸或资源浪费。以下是如何在 React + TypeScript 前端和 Node.js + Express 后端中防止验证码被刷的深度分析,并结合代码实现。


1. 常见的攻击场景

  1. 短信轰炸

    • 攻击者通过脚本频繁请求验证码,导致用户手机收到大量短信。
  2. 验证码滥用

    • 攻击者通过暴力破解或重放攻击,尝试破解验证码。
  3. 资源耗尽

    • 频繁请求验证码可能导致服务器资源耗尽,影响正常用户。

2. 防御措施

2.1 前端防御措施

  1. 增加图形验证码

    • 在发送短信验证码之前,要求用户输入图形验证码,防止自动化脚本。
  2. 请求频率限制

    • 在前端限制用户点击"发送验证码"按钮的频率(如 60 秒内只能点击一次)。
  3. IP 和设备指纹识别

    • 记录用户 IP 和设备信息,用于后端分析和限制。

2.2 后端防御措施

  1. 请求频率限制

    • 使用 Redis 或内存缓存记录每个手机号或 IP 的请求频率,限制单位时间内的请求次数。
  2. 验证码有效期

    • 设置验证码的较短有效期(如 5 分钟),减少被滥用的风险。
  3. 验证码使用次数限制

    • 每个验证码只能使用一次,使用后立即失效。
  4. 黑名单机制

    • 将频繁请求验证码的 IP 或手机号加入黑名单,暂时禁止其请求。
  5. 日志监控

    • 记录验证码请求日志,监控异常行为。

3. 前端实现(React + TypeScript)

3.1 增加图形验证码

在发送短信验证码之前,要求用户输入图形验证码。

tsx 复制代码
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 请求频率限制

在前端限制用户点击"发送验证码"按钮的频率。

tsx 复制代码
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 的请求频率。

javascript 复制代码
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 中存储验证码,并设置有效期和使用次数。

javascript 复制代码
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 或手机号加入黑名单。

javascript 复制代码
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、设备指纹、用户行为等多维度进行限制。

通过以上措施,可以有效防止手机验证码被刷,保障系统的安全性和稳定性。

相关推荐
程序员爱钓鱼1 分钟前
Go语言实战案例-创建模型并自动迁移
后端·google·go
javachen__7 分钟前
SpringBoot整合P6Spy实现全链路SQL监控
spring boot·后端·sql
阿珊和她的猫2 小时前
v-scale-scree: 根据屏幕尺寸缩放内容
开发语言·前端·javascript
uzong6 小时前
技术故障复盘模版
后端
GetcharZp6 小时前
基于 Dify + 通义千问的多模态大模型 搭建发票识别 Agent
后端·llm·agent
加班是不可能的,除非双倍日工资6 小时前
css预编译器实现星空背景图
前端·css·vue3
桦说编程6 小时前
Java 中如何创建不可变类型
java·后端·函数式编程
IT毕设实战小研6 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
wyiyiyi7 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
gnip7 小时前
vite和webpack打包结构控制
前端·javascript