文章

邮箱验证码

邮箱验证码

在注册账号、重置密码、验证身份等场景下,需要利用验证码验证用户身份,在确认用户操作后再执行下一步操作。

拆解为关键节点:

  1. 生成验证码
  2. 设计逻辑验证验证码有效期
  3. 利用SMTP服务发送邮件
  4. 验证验证码有效性

生成验证码

简单的六位随机数

private String genCode(){
        //生成随机数6位
        Random random = new Random();
        StringBuilder buffer = new StringBuilder();
        for (int i = 0; i < 6; i++) {
            int number = random.nextInt(10);
            buffer.append(number);
        }
        return String.valueOf(buffer);
    }

存储验证码

向redis中保存邮箱、发送时间和验证码,便于后续使用的时候验证。

@PostMapping("/registerCode")
    public Response<Void> sendCode(@RequestBody @Valid RegisterCodeDTO registerDTO) throws MessagingException {
		//省略GoogleChapta相关代码
        Users usersParams = new Users();
        usersParams.setEmail(registerDTO.getEmail());
		//验证用户是否重复
        Users usersRet = usersService.queryByUsers(usersParams);
        if (usersRet != null){
            return new Response.Builder<Void>()
                    .code(ResponseCode.USERS_EMAIL_ALREADY_REGISTER)
                    .build();
        }
		//生成验证码
        String code = genCode();
        long now = new Date().getTime();
        String token = CryptoUtil.encodeMD5Hex(registerDTO.getEmail()+now);
		//发送邮件
        sendCode(registerDTO.getEmail(),code);

		//把邮箱-时间-验证码存入redis
        stringRedisTemplate.opsForValue().set(RedisServicePreFix.USER_SERVICE +
                RedisEntityPreFix.REGISTER_CODE + token,code,300,TimeUnit.SECONDS);
    
		return new Response.Builder<Void>()
                .code(ResponseCode.COMMON_SUCCESS)
                .message("发送成功, 请登录邮箱及时查看")
                .build();
    }

发送验证码

代码如下

private void sendCode(String email,String code) throws MessagingException {
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,true);
        helper.setFrom("xx工作室<"+mailUsername+">");
        helper.setTo(email);
        helper.setSubject("验证码");
        Map<String,Object> map = new HashMap<>();
        map.put("email",email);
        map.put("code",code);
        helper.setText(emailUtil.builderContent("code.html",map),true);
        mailSender.send(mimeMessage);
    }

MimeMessageHelper​类是SpringMail组件提供的,可以用模板html填充的方式组件邮件内容,然后直接发送即可。对接邮件服务器相关配置在Spring配置文件中,这种敏感信息建议从环境变量读取,以防一不小心传给别人然后忘了或者不小心传到Github了。

#邮件测试
spring.mail.username=${MAIL_USER:}
spring.mail.password=${MAIL_PASSWORD:}
spring.mail.properties.mail.smtp.auth=true
spring.mail.protocol=smtps
spring.mail.properties.mail.ssl.enable=true
#spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
#spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.default-encoding=UTF-8
spring.mail.port=465
spring.mail.host=${MAIL_HOST:}

验证验证码有效性

这里直接写一段Lua,去Redis里面读出结果对比一下就行。

private Long checkCode(String key, String code) {
        String codeLua = "local code=redis.call('get',KEYS[1]) "+
                "if code==false then "+
                "return 0 "+
                "end "+
                "if code~=ARGV[1] then "+
                "return 0 "+
                "end "+
                "redis.call('del',KEYS[1]) "+
                "return 1 ";
        DefaultRedisScript<Long> codeScript = new DefaultRedisScript<>(codeLua, Long.class);
        return stringRedisTemplate.execute(codeScript,
                Collections.singletonList(key),
                code
        );
    }

License:  CC BY 4.0