用户注册短信验证码(防刷)

用户注册短信验证码防刷

1.使用场景

  • 注册验证
  • 信息变更: 修改密码、手机号等个人信息时,确保是用户本人操作,进行短信验证
  • 找回密码
  • 动态登录

2.防刷目的

  • 防止被黑客利用进行短信轰炸,防止浪费短信余额

3.防刷手段

  • 前端图形验证码
  • 前端滑动类
  • 前端点击类
  • 单个手机号请求限制
  • 单个ip请求限制
  • 手机号码真实性限制

4.实战操作

下面介绍一下图形验证码方式

  1. 引入依赖
            <dependency>
                <groupId>cn.hutoolgroupId>
                <artifactId>hutool-allartifactId>
                <version>5.7.16version>
            dependency>
  1. 短信配置

    这里使用的是阿里云的短信服务

    链接:https://market.aliyun.com/products/57000002/cmapi00046920.html?spm=5176.2020520132.101.1.14247218o2uxXD#sku=yuncode4092000001

    #配置短信服务
    sms:
      #添加aliyun上的appCode
      app-code: xxxxxxxxxxxxxxxxx
      template-id: M72CB42894
    
    // 配置类
    @ConfigurationProperties("sms")
    @Component
    @Data
    public class SmsConfig {
        private String appCode;
        private String templateId;
    }
    
    // 短信发送组件
    @Component
    @Slf4j
    public class SmsComponent {
        /**
         * 发送地址
         */
        private static final String URL_TEMPLATE = "https://jmsms.market.alicloudapi.com/sms/send?mobile=%s&templateId=%s&value=%s";
    
        @Autowired
        private RestTemplate restTemplate;
    
        @Autowired
        private SmsConfig smsConfig;
    
        /**
         * @param to         手机号
         * @param templateId 短信模板id
         * @param value      验证码
         */
        public void send(String to, String templateId, String value) {
            String url = String.format(URL_TEMPLATE, to, templateId, value);
    
            HttpHeaders headers = new HttpHeaders();
            //最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105
            headers.set(HttpHeaders.AUTHORIZATION, "APPCODE " + smsConfig.getAppCode());
    
            HttpEntity<String> entity = new HttpEntity<>(headers);
            ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);
    
            log.info("url={},body={}", url, response.getBody());
            if (response.getStatusCode() == HttpStatus.OK) {
                log.info("发送短信成功,响应信息:{}", response.getBody());
            } else {
                log.error("发送短信失败,响应信息:{}", response.getBody());
            }
        }
    }
    
    // 发送短信验证码请求对象
    @Data
    public class SendCodeRequest {
        /**
         * 图形验证码
         */
        private String captcha;
    
        /**
         * 登录的手机号/邮箱
         */
        private String to;
    }
    
    // 验证码类型
    public enum SendCodeEnum {
        // 用于注册
        USER_REGISTER;
    }
    
  2. 图形验证码接口

        @Autowired
        private StringRedisTemplate redisTemplate;
    
        /**
         * 图形验证码10分钟有效
         */
        private static final Duration CAPTCHA_CODE_EXPIRED = Duration.ofMinutes(10);
    
        /**
         * 获取图形验证码
         *
         * @return
         */
        @GetMapping("/captcha")
        public void getCaptcha(HttpServletRequest request, HttpServletResponse response) {
            // 利用hutool工具包,生成图形验证码对象
            LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100, 4, 150);
            try {
                // 验证码
                String code = lineCaptcha.getCode();
                // 存储到redis中
                redisTemplate.opsForValue().set(getCaptchaKey(request), code, CAPTCHA_CODE_EXPIRED);
                log.info("图形验证码code:{}", code);
                lineCaptcha.write(response.getOutputStream());
            } catch (IOException e) {
                log.error("图形验证码出错:{}", e.getMessage());
            }
        }
    
        /**
         * 生成/获取图形验证码缓存key
         *
         * @param request
         * @return
         */
        private String getCaptchaKey(HttpServletRequest request) {
            // 根据request获取到ip
            String ip = CommonUtil.getIpAddr(request);
            String userAgent = request.getHeader(HttpHeaders.USER_AGENT);
            String key = "zhuyz:captcha:" + CommonUtil.MD5(ip + userAgent);
            return key;
        }
    
  3. 短信验证码接口

        @Autowired
        private StringRedisTemplate redisTemplate;
    
    /**
         * 发送短信验证码
         *
         * @param sendCodeRequest
         * @param request
         * @return
         */
        @PostMapping("/send_code")
        public JsonData sendCode(@RequestBody SendCodeRequest sendCodeRequest, HttpServletRequest request) {
            // 1.校验图形验证码
            String captchaKey = getCaptchaKey(request);
            String captchaCacheValue = redisTemplate.opsForValue().get(captchaKey);
            if (ObjectUtil.isNull(captchaCacheValue)
                    && ObjectUtil.isNull(sendCodeRequest.getCaptcha())
                    && !StrUtil.equalsIgnoreCase(captchaCacheValue, sendCodeRequest.getCaptcha())) {
                return JsonData.buildResult(BizCodeEnum.CODE_CAPTCHA_ERROR);
            }
            // 2.删除图形验证码key&发送短信
            redisTemplate.delete(captchaKey);
            return notifyService.sendCode(SendCodeEnum.USER_REGISTER, sendCodeRequest.getTo());
        }
    
     /**
         * 短信验证码10分钟有效期
         */
        public static final Duration CODE_EXPIRED = Duration.ofMinutes(10);
    
        @Autowired
        private StringRedisTemplate redisTemplate;
    
        @Autowired
        private SmsComponent smsComponent;
    
        @Autowired
        private SmsConfig smsConfig;
    
        /**
         * 1.判断redis是否存在对应的短信验证码
         *      存在并且时间差小于60s,则为重复发送
         *      反之则生成短信验证码,缓存并且发送短信
         *
         * @param sendCodeEnum
         * @param to
         * @return
         */
        @Override
        public JsonData sendCode(SendCodeEnum sendCodeEnum, String to) {
            // 1.判断是否重复发送短信
            String cacheCodeKey = String.format(RedisKey.CHECK_CODE_KEY, sendCodeEnum.name(), to);
            // 数据格式:smsCode_timestamp
            String cacheCodeValue = redisTemplate.opsForValue().get(cacheCodeKey);
            if (StrUtil.isNotBlank(cacheCodeValue)) {
                List<String> split = StrUtil.split(cacheCodeValue, "_");
                // 时间差
                long gap = System.currentTimeMillis() - Long.parseLong(split.get(1));
                if (gap < 60) {
                    // 重复发送
                    return JsonData.buildResult(BizCodeEnum.CODE_LIMITED);
                }
            }
    
            // 2.生成短信验证码,缓存并且发送短信
            String smsCode = RandomUtil.randomNumbers(6);
            // 新的缓存的值
            String value = StrUtil.join("_", smsCode, System.currentTimeMillis());
            redisTemplate.opsForValue().set(cacheCodeKey, value, CODE_EXPIRED);
    
            if (Validator.isEmail(to)) {
                // 发送邮件
    
            } else if (Validator.isMobile(to)) {
                // 发送短信
                smsComponent.send(to, smsConfig.getTemplateId(), smsCode);
            }
            return JsonData.buildSuccess();
        }
    

你可能感兴趣的:(java,java,spring,boot)