Java实现发送验证码次数限制功能

简介

在用户登录功能中,为了增加安全性和验证用户的身份,常常会添加发送验证码的功能。本文将介绍如何在开发中实现登录发送验证码功能。

工具

  • 后端:Redis,阿里云

简介(主要介绍限次数功能的实现)

在用户注册、登录等功能中,为了防止恶意发送验证码,常常需要对发送验证码的次数进行限制。本文将介绍如何在开发中实现限制发送验证码次数的功能。

可以使用以下两种方式实现:计数器校验,阿里云返回状态校验

1. 计数器校验

  • 借助Redis工具的过期功能,每次向该手机号发送验证码之后,使用计数器增加一次次数,将当前次数同步更新到Redis中,下次在发送验证码时,需要对发送验证码次数进行判断,如果超过限制次数,则拒绝发送验证码。
  • (1) 工具类
  • //验证码生成工具类
    public class GenerateVerificationCodeUtil {
        private static final String CHARACTERS = "0123456789";
    
        /***
         * 生成随机验证码
         * @param length 验证码长度
         * @return code
         */
        public static String generateVerificationCode(int length) {
            StringBuilder code = new StringBuilder();
            Random random = new Random();
    
            IntStream.range(0, length).map(i -> random.nextInt(CHARACTERS.length())).forEach(randomIndex -> {
                char randomChar = CHARACTERS.charAt(randomIndex);
                code.append(randomChar);
            });
    
            return code.toString();
        }
    }

    (2)阿里云工具类

  • @Component
    @Slf4j
    public class AliyunSmsLoader {
    //配置可以查看阿里云官方教程
        private String accessKey;
        private String secret;
        private String sysDomain;
        private String sysVersion;
        private String regionId;
        private Boolean isOpen;
    
        private DefaultProfile profile = null;
    
        /**
         * 发送短信验证码
         * @param phone 电话号码
         * @param code 验证码
         */zhe
        public boolean sendCode(String phone, String code) {
            try {
                if (isOpen){
                    if (profile == null){
                        profile = DefaultProfile.getProfile(regionId, accessKey, secret);
                    }
    
                    Map<String, String> data = new HashMap<>();
                    data.put("code", code);
                    CommonRequest request = new CommonRequest();
                    request.setSysMethod(MethodType.POST);
                    request.setSysDomain(sysDomain);
                    request.setSysVersion(sysVersion);
                    request.setSysAction("SendSms");
                    request.putQueryParameter("RegionId", regionId);
                    request.putQueryParameter("PhoneNumbers", phone);
                    request.putQueryParameter("SignName", "Clinflash");
                    request.putQueryParameter("TemplateCode", SmsTemplateConstants.TemplateEnum.CODE_ZH.getCode());
                    request.putQueryParameter("TemplateParam", JSONObject.toJSONString(data));
    
                    IAcsClient client = new DefaultAcsClient(this.profile);
                    CommonResponse response = client.getCommonResponse(request);
                    Map<String, Object> resMap = (Map<String, Object>) JsonUtils.toObject(response.getData(), Map.class);
                    if (resMap != null && resMap.containsKey("Message") && "OK".equals(String.valueOf(resMap.get("Message")))){
                        return true;
                    }
                }
            } catch (Exception e) {
                log.error("send sms code error", e);
            }
            return false;
        }
    }

  • (3)Redis工具类

  • @Component
    public class RedisUtil {
        @Resource
        private RedisTemplate<String, String> redisTemplate;
    
        public void set(String key, String value, long timeout) {
            redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
        }
    
        public String get(String key) {
            return redisTemplate.opsForValue().get(key);
        }
    
        public void delete(String key) {
            redisTemplate.delete(key);
        }
    
        public boolean expire(String key, long timeout, TimeUnit timeUnit) {
            return Boolean.TRUE.equals(redisTemplate.expire(key, timeout, timeUnit));
        }
    
        public boolean hasKey(String key) {
            return Boolean.TRUE.equals(redisTemplate.hasKey(key));
        }
    }
    

  • (4)计数器

  •  public boolean checkSendLimit(String key) {
    //检查是否超过限制
            String key1 = "SEND_LIMIT_" + key;//发送次数在redis中的key标识
            int sendCount = 0;
            int maxSendCount = 5; // 最大发送次数限制
            // 获取当前发送次数
            Object countObj = redisUtil.get(key1);
            if (countObj != null) {
                sendCount = Integer.parseInt(countObj.toString());
            }
    
            return sendCount >= maxSendCount; // 超过发送次数限制返回 true,否则返回 false
        }
    
        // 增加验证码发送次数
        public void incrementSendCount(String key) {
            // 获取当前发送次数
            Object countObj = redisUtil.get(key);
            int sendCount = (countObj != null) ? Integer.parseInt(countObj.toString()) : 0;
            // 增加发送次数
            sendCount++;
            redisUtil.set(key, String.valueOf(sendCount), 3600L); // 存储一小时内的发送次数
        }

    (5)发送验证码

  • //发送验证码功能    
    public boolean sendCode(String number) {
            String key = "SEND_LIMIT_" + number;
            String code = GenerateVerificationCodeUtil.generateVerificationCode(6);
            aliyunSmsLoader.sendCode(number, code);
                // 将当前手机号和验证码绑定放到 Redis 中,有效时间五分钟
            redisUtil.set(number, code, 300L);
    
            incrementSendCount(key); // 增加发送次数
    
            log.info("code: {}", code);
            return true;
        }
    

  • 这样每次在发送验证码之前先校验次数,如果超过限制次数,则拒绝发送。
  • boolean isExceedLimit = userService.checkSendLimit(number);
                    //验证码次数校验,一小时内最多5次机会
                    if (isExceedLimit) {
                        return BaseResult.fail("4005", MessageUtils.get("login.send.limit.exceeded"));
                    }

2. 阿里云返回状态校验

  • 在调用阿里云服务的接口时,会有返回的状态。
  • 根据状态可以做比较,根据状态值不一样说明发送到达上限,这里就是对返回信息状态进行了校验
  • IAcsClient client = new DefaultAcsClient(this.profile);
                    CommonResponse response = client.getCommonResponse(request);
                    Map<String, Object> resMap = (Map<String, Object>) JsonUtils.toObject(response.getData(), Map.class);
                    if (resMap != null && resMap.containsKey("Message") && "OK".equals(String.valueOf(resMap.get("Message")))){
                        return true;
                    }
                }

    踩坑

  • 阿里云短信触发限制后,会出现手机号收不到验证码但是提示代码显示正常发送情况,以及同一个手机号接收验证码过多也会触发运营商的限制,尽量不要触发级控,保证在可控情景内

纯小白,大佬勿喷也欢迎指点写文技巧

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
实现验证码功能可以使用Java的AWT和BufferedImage类来生成图像验证码。以下是一个简单的实现示例: ```java import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import java.awt.image.BufferedImage; import java.util.Random; public class CaptchaGenerator { private static final int WIDTH = 120; // 图片宽度 private static final int HEIGHT = 40; // 图片高度 private static final int CODE_LENGTH = 4; // 验证码长度 private static final String CODES = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; // 验证码字符集 private static final Random random = new Random(); public static BufferedImage generate() { // 创建图像对象 BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB); Graphics g = image.getGraphics(); // 填充背景色 g.setColor(Color.WHITE); g.fillRect(0, 0, WIDTH, HEIGHT); // 生成随机验证码 StringBuilder code = new StringBuilder(); for (int i = 0; i < CODE_LENGTH; i++) { char c = CODES.charAt(random.nextInt(CODES.length())); code.append(c); } // 绘制验证码 g.setColor(Color.BLACK); g.setFont(new Font("Arial", Font.BOLD, 20)); for (int i = 0; i < CODE_LENGTH; i++) { char c = code.charAt(i); g.drawString(String.valueOf(c), 20 + i * 25, 25); } // 添加干扰线 g.setColor(Color.GRAY); for (int i = 0; i < 5; i++) { int x1 = random.nextInt(WIDTH); int y1 = random.nextInt(HEIGHT); int x2 = random.nextInt(WIDTH); int y2 = random.nextInt(HEIGHT); g.drawLine(x1, y1, x2, y2); } // 释放资源 g.dispose(); return image; } } ``` 调用`generate()`方法即可生成一个包含随机验证码的BufferedImage对象。可以将该对象输出到HTTP响应中,以便客户端进行验证。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

先锋 Coder

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值