You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在React Native中对NodeJS OTP接口二次及以上请求触发reCaptcha验证

解决方案:基于请求计数的reCaptcha触发机制

一、判断用户是否第二次及以上调用API

核心思路是在服务端维护用户的请求计数(前端计数不可靠,易被篡改),通过用户唯一标识绑定请求次数,推荐用Redis存储(高效支持自增和过期策略)。

实现步骤:

  1. 确定用户唯一标识:
    • 优先用请求中的手机号(OTP接口通常关联手机号);
    • 若用户未登录,可使用设备ID + IP地址的组合,避免同一IP下不同用户被误统计。
  2. 服务端计数逻辑:
    • 每次请求进入时,对用户标识对应的Redis键做自增;
    • 首次计数时设置过期时间(与OTP有效期一致,比如15分钟),避免长期无效计数占用存储。

代码示例(NodeJS + Redis):

const Redis = require('ioredis');
const redis = new Redis(process.env.REDIS_URL);

// 封装请求计数检查函数
async function getOTPRequestCount(userIdentifier) {
  const redisKey = `otp:request:count:${userIdentifier}`;
  // 自增计数,原子操作避免并发问题
  const requestCount = await redis.incr(redisKey);
  // 首次请求时设置过期时间
  if (requestCount === 1) {
    await redis.expire(redisKey, 900); // 15分钟 = 900秒
  }
  return requestCount;
}

// 在OTP接口中调用
app.post('/api/send-otp', async (req, res) => {
  const { phone } = req.body;
  // 用手机号作为用户标识
  const requestCount = await getOTPRequestCount(phone);
  
  // 将计数挂载到请求对象,供后续逻辑使用
  req.otpRequestCount = requestCount;
  
  // 继续后续OTP发送逻辑(暂时省略)
});

二、仅在请求次数≥2时触发reCaptcha验证

需要前后端配合:服务端判断计数后告知前端是否需要验证,前端完成验证后携带token重新请求,服务端校验token有效性。

服务端逻辑:

  1. 当请求次数≥2时,返回needCaptcha: true给前端,提示需要验证;
  2. 前端携带captchaToken重新请求时,调用Google reCaptcha官方接口校验token有效性,校验通过后再发送OTP。

服务端验证reCaptcha代码示例:

// 封装reCaptcha验证函数
async function verifyRecaptchaToken(token) {
  const secretKey = process.env.RECAPTCHA_SECRET_KEY;
  try {
    const response = await fetch(
      `https://www.google.com/recaptcha/api/siteverify?secret=${secretKey}&response=${token}`,
      { method: 'POST' }
    );
    const result = await response.json();
    return result.success;
  } catch (error) {
    console.error('reCaptcha验证失败:', error);
    return false;
  }
}

// 更新OTP接口逻辑
app.post('/api/send-otp', async (req, res) => {
  const { phone, captchaToken } = req.body;
  const requestCount = await getOTPRequestCount(phone);

  // 次数≥2时校验reCaptcha
  if (requestCount >= 2) {
    if (!captchaToken) {
      return res.status(400).json({
        needCaptcha: true,
        message: '请完成人机验证后重试'
      });
    }
    const isCaptchaValid = await verifyRecaptchaToken(captchaToken);
    if (!isCaptchaValid) {
      return res.status(400).json({ message: '人机验证失败,请重试' });
    }
  }

  // 执行OTP发送逻辑
  try {
    await sendOTPService(phone); // 自定义的OTP发送服务
    res.json({ message: 'OTP已发送至您的手机' });
  } catch (error) {
    res.status(500).json({ message: 'OTP发送失败,请稍后重试' });
  }
});

React Native前端实现:

使用适配移动端的reCaptcha库(如react-native-google-recaptcha),根据服务端返回的needCaptcha状态触发验证。

import React, { useState, useRef } from 'react';
import { View, TextInput, Button, Alert, StyleSheet } from 'react-native';
import Recaptcha from 'react-native-google-recaptcha';

const SendOTPScreen = () => {
  const [phoneNumber, setPhoneNumber] = useState('');
  const [showCaptcha, setShowCaptcha] = useState(false);
  const recaptchaRef = useRef(null);

  const handleSendOTP = async () => {
    if (!phoneNumber.trim()) {
      Alert.alert('提示', '请输入手机号');
      return;
    }

    try {
      const response = await fetch('https://your-api-domain.com/api/send-otp', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ phone: phoneNumber })
      });
      const data = await response.json();

      if (data.needCaptcha) {
        setShowCaptcha(true);
        recaptchaRef.current?.open();
      } else if (response.ok) {
        Alert.alert('成功', 'OTP已发送');
      } else {
        Alert.alert('错误', data.message);
      }
    } catch (error) {
      Alert.alert('错误', '网络请求失败,请检查网络');
    }
  };

  const handleCaptchaSuccess = async (token) => {
    setShowCaptcha(false);
    try {
      const response = await fetch('https://your-api-domain.com/api/send-otp', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          phone: phoneNumber,
          captchaToken: token
        })
      });
      const data = await response.json();

      if (response.ok) {
        Alert.alert('成功', 'OTP已发送');
      } else {
        Alert.alert('错误', data.message);
      }
    } catch (error) {
      Alert.alert('错误', '验证请求失败');
    }
  };

  return (
    <View style={styles.container}>
      <TextInput
        style={styles.input}
        placeholder="请输入手机号"
        value={phoneNumber}
        onChangeText={setPhoneNumber}
        keyboardType="phone-pad"
      />
      <Button title="发送OTP" onPress={handleSendOTP} />

      <Recaptcha
        ref={recaptchaRef}
        siteKey="your-recaptcha-site-key"
        onVerify={handleCaptchaSuccess}
        onExpire={() => Alert.alert('提示', '验证码已过期,请重新验证')}
        containerStyle={{ display: showCaptcha ? 'flex' : 'none' }}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    justifyContent: 'center'
  },
  input: {
    height: 40,
    borderColor: '#ccc',
    borderWidth: 1,
    marginBottom: 20,
    paddingHorizontal: 10
  }
});

export default SendOTPScreen;

注意事项:

  • 用户标识的安全性:避免仅用IP作为标识,防止同一IP下多个用户被误限制;
  • Redis过期策略:过期时间需与OTP有效期匹配,避免用户间隔过久请求时计数仍有效;
  • 密钥管理:reCaptcha的密钥和Redis连接信息需存储在环境变量中,禁止硬编码;
  • 异常处理:服务端需处理reCaptcha验证接口的网络异常,避免因第三方服务不可用导致接口故障。

内容的提问来源于stack exchange,提问作者warCommander

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 04:01:19