Java 防止短信验证码接口被盗刷问题解决

 更新时间:2026年02月07日 10:12:46   作者:uup  
本文主要介绍了Java 防止短信验证码接口被盗刷问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

一、Bug 场景

在一个基于 Java 的 Web 应用中,用户注册或找回密码等功能依赖短信验证码进行身份验证。然而,近期发现短信验证码接口被恶意用户频繁调用,导致大量短信被发送,不仅增加了运营成本,还影响了正常用户的使用体验,甚至可能因触发运营商短信发送限制而导致服务不可用。

二、代码示例

短信发送服务类(有缺陷)

import java.util.Random;

public class SmsService {
    public void sendSms(String phoneNumber) {
        // 生成 6 位随机验证码
        int code = new Random().nextInt(900000) + 100000;
        System.out.println("向 " + phoneNumber + " 发送短信验证码: " + code);
        // 实际应用中应调用短信发送 API 发送验证码
    }
}

短信验证码接口控制器(有缺陷)

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class SmsVerificationCodeController {
    private SmsService smsService = new SmsService();

    public void sendVerificationCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String phoneNumber = request.getParameter("phoneNumber");
        if (phoneNumber != null) {
            smsService.sendSms(phoneNumber);
            response.getWriter().println("短信验证码已发送");
        } else {
            response.getWriter().println("手机号不能为空");
        }
    }
}

三、问题描述

  1. 预期行为:正常用户在需要时能够获取短信验证码,且恶意用户无法通过频繁调用接口来盗刷短信验证码,确保短信发送资源合理使用,服务稳定运行。
  2. 实际行为:恶意用户可以通过编写自动化脚本,不断调用短信验证码接口,导致大量不必要的短信被发送。这是因为当前接口没有任何限制机制,无论是正常用户还是恶意用户,只要提供了手机号,就可以无限制地获取短信验证码。

四、解决方案

  1. 增加频率限制:通过记录用户请求频率,限制同一手机号在一定时间内的短信发送次数。可以使用 Redis 来存储手机号的发送记录和时间戳。
import redis.clients.jedis.Jedis;
import java.util.Random;

public class SmsService {
    private static final int MAX_SENDS_PER_MINUTE = 3; // 每分钟最多发送 3 次
    private Jedis jedis;

    public SmsService(Jedis jedis) {
        this.jedis = jedis;
    }

    public boolean canSendSms(String phoneNumber) {
        String key = "sms:limit:" + phoneNumber;
        Long count = jedis.incr(key);
        if (count == 1) {
            jedis.expire(key, 60); // 设置过期时间为 1 分钟
        }
        return count <= MAX_SENDS_PER_MINUTE;
    }

    public void sendSms(String phoneNumber) {
        if (canSendSms(phoneNumber)) {
            int code = new Random().nextInt(900000) + 100000;
            System.out.println("向 " + phoneNumber + " 发送短信验证码: " + code);
            // 实际应用中应调用短信发送 API 发送验证码
        } else {
            System.out.println("发送频率过高,请稍后再试");
        }
    }
}

修改后的短信验证码接口控制器

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import redis.clients.jedis.Jedis;
import java.io.IOException;

public class SmsVerificationCodeController {
    private SmsService smsService;

    public SmsVerificationCodeController(Jedis jedis) {
        this.smsService = new SmsService(jedis);
    }

    public void sendVerificationCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String phoneNumber = request.getParameter("phoneNumber");
        if (phoneNumber != null) {
            smsService.sendSms(phoneNumber);
            response.getWriter().println("短信验证码已发送");
        } else {
            response.getWriter().println("手机号不能为空");
        }
    }
}
  1. 使用图形验证码:在发送短信验证码之前,要求用户先输入图形验证码。用户只有正确输入图形验证码后,才能请求短信验证码,增加恶意调用的难度。
// 图形验证码生成和验证逻辑(示例代码,实际需更完善实现)
public class CaptchaService {
    public String generateCaptcha() {
        // 生成图形验证码的逻辑,返回验证码字符串
        return "1234";
    }

    public boolean verifyCaptcha(String inputCaptcha, String storedCaptcha) {
        return inputCaptcha.equals(storedCaptcha);
    }
}

再次修改后的短信验证码接口控制器

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import redis.clients.jedis.Jedis;
import java.io.IOException;

public class SmsVerificationCodeController {
    private SmsService smsService;
    private CaptchaService captchaService;

    public SmsVerificationCodeController(Jedis jedis) {
        this.smsService = new SmsService(jedis);
        this.captchaService = new CaptchaService();
    }

    public void sendVerificationCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String phoneNumber = request.getParameter("phoneNumber");
        String inputCaptcha = request.getParameter("captcha");
        if (phoneNumber != null) {
            String storedCaptcha = "1234"; // 实际应用中应从 session 或其他存储中获取
            if (captchaService.verifyCaptcha(inputCaptcha, storedCaptcha)) {
                smsService.sendSms(phoneNumber);
                response.getWriter().println("短信验证码已发送");
            } else {
                response.getWriter().println("图形验证码错误");
            }
        } else {
            response.getWriter().println("手机号不能为空");
        }
    }
}
  1. IP 访问限制:记录请求的 IP 地址,限制同一 IP 在一定时间内对短信验证码接口的访问次数。同样可以使用 Redis 来实现。
import redis.clients.jedis.Jedis;
import java.util.Random;

public class SmsService {
    private static final int MAX_REQUESTS_PER_IP_PER_MINUTE = 10; // 每分钟每个 IP 最多请求 10 次
    private Jedis jedis;

    public SmsService(Jedis jedis) {
        this.jedis = jedis;
    }

    public boolean canSendSmsFromIp(String ip) {
        String key = "sms:ip:limit:" + ip;
        Long count = jedis.incr(key);
        if (count == 1) {
            jedis.expire(key, 60); // 设置过期时间为 1 分钟
        }
        return count <= MAX_REQUESTS_PER_IP_PER_MINUTE;
    }

    // 其他方法不变
}

最终修改后的短信验证码接口控制器

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import redis.clients.jedis.Jedis;
import java.io.IOException;

public class SmsVerificationCodeController {
    private SmsService smsService;
    private CaptchaService captchaService;

    public SmsVerificationCodeController(Jedis jedis) {
        this.smsService = new SmsService(jedis);
        this.captchaService = new CaptchaService();
    }

    public void sendVerificationCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String phoneNumber = request.getParameter("phoneNumber");
        String inputCaptcha = request.getParameter("captcha");
        String clientIp = request.getRemoteAddr();

        if (phoneNumber != null) {
            String storedCaptcha = "1234"; // 实际应用中应从 session 或其他存储中获取
            if (captchaService.verifyCaptcha(inputCaptcha, storedCaptcha) && smsService.canSendSmsFromIp(clientIp)) {
                smsService.sendSms(phoneNumber);
                response.getWriter().println("短信验证码已发送");
            } else if (!captchaService.verifyCaptcha(inputCaptcha, storedCaptcha)) {
                response.getWriter().println("图形验证码错误");
            } else {
                response.getWriter().println("请求频率过高,请稍后再试");
            }
        } else {
            response.getWriter().println("手机号不能为空");
        }
    }
}

到此这篇关于Java 防止短信验证码接口被盗刷问题解决的文章就介绍到这了,更多相关Java 防止短信验证码接口被盗刷内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringCloud注册中心部署Eureka流程详解

    SpringCloud注册中心部署Eureka流程详解

    Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的
    2022-11-11
  • Eclipse代码格式化设置简单介绍

    Eclipse代码格式化设置简单介绍

    这篇文章主要介绍了Eclipse代码格式化设置简单介绍,具有一定参考价值,需要的朋友可以了解下。
    2017-10-10
  • ArrayList的自动扩充机制实例解析

    ArrayList的自动扩充机制实例解析

    本文主要介绍了ArrayList的自动扩充机制,由一个题目切入主题,逐步向大家展示了ArrayList的相关内容,具有一定参考价值,需要的朋友可以了解下。
    2017-10-10
  • Java如何获取客户端mac地址

    Java如何获取客户端mac地址

    在用户登录时,通过获取IP地址来识别计算机的MAC地址,然后将用户账号与该MAC地址进行绑定,确保每个账号只能在一台特定的计算机上登录,增强系统安全性,这种方法适用于需要严格账户安全管理的场景
    2024-09-09
  • 详解SpringBoot接收参数的五种形式

    详解SpringBoot接收参数的五种形式

    在Spring Boot中,接收参数可以通过多种方式实现,本文给大家介绍了SpringBoot接收参数的五种形式,并通过代码和图文给大家介绍的非常详细,需要的朋友可以参考下
    2024-03-03
  • Java可重入锁reentrantLock解析

    Java可重入锁reentrantLock解析

    这篇文章主要介绍了Java可重入锁reentrantLock解析,reentrantLock跟synchronized代码结构差不多,只是多了一个lock和unlock的过程,需要的朋友可以参考下
    2023-12-12
  • 一文详解Java中的监听器(Listener)

    一文详解Java中的监听器(Listener)

    这篇文章主要介绍了Java中监听器(Listener)的相关资料,监听器模式是一种设计模式,用于处理异步事件和通知,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-03-03
  • Java调用MySQL数据库的存储过程和自定义函数及区别解析

    Java调用MySQL数据库的存储过程和自定义函数及区别解析

    本文介绍了存储过程和自定义函数的概念、特性和构建方法,并通过Java代码展示了如何调用存储过程和自定义函数,最后,对比了存储过程和自定义函数的区别,感兴趣的朋友跟随小编一起看看吧
    2025-12-12
  • 在Java中Scanner的用法总结

    在Java中Scanner的用法总结

    这篇文章主要介绍了在Java中Scanner的用法总结,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10
  • Java中拷贝list数组几种常见的方法

    Java中拷贝list数组几种常见的方法

    这篇文章主要给大家介绍了关于Java中拷贝list数组几种常见的方法,在Java中,List是一个接口,它有多个实现类,如ArrayList、LinkedList等,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2023-08-08

最新评论