12| 注册安全攻防-邮箱发送验证码 小滴课堂讲师 2024年07月31日 预计阅读 4 分钟 原文 ### 验证码防刷设计方案 - 前端增加倒计时60s - 后端增加校验 - 通过前端传的发送着与接收者或者表示消息唯一性为key,查询对应存储的验证码(此验证码包含时间戳) - 如果存在,取出来来比较时间戳是否超过60s,超过60s之后才能再次获取验证码。code_time - 根据传入的是手机号还是邮箱来处理发送什么类型。 ### 获取随机数字为验证码 添加到`CommonUtil` ```java /** * 获取验证码随机数 * * @param length 长度 */ public static String getRandomCode(int length) { String sources = "0123456789"; Random random = new Random(); StringBuilder sb = new StringBuilder(); for (int j = 0; j < length; j++) { sb.append(sources.charAt(random.nextInt(9))); } return sb.toString(); } ``` ### 判断入参是手机号还是邮箱CheckUtil ```java public class CheckUtil { /** * 邮箱正则 */ private static final Pattern MAIL_PATTERN = Pattern.compile("^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$"); /** * 手机号正则,暂时未用 */ private static final Pattern PHONE_PATTERN = Pattern.compile("^((13[0-9])|(15[^4,\\D])|(18[0,5-9]))\\d{8}$"); /** * @param email 邮箱 */ public static boolean isEmail(String email) { if (null == email || "".equals(email)) { return false; } Matcher m = MAIL_PATTERN.matcher(email); return m.matches(); } /** * @param phone 手机号 */ public static boolean isPhone(String phone) { if (null == phone || "".equals(phone)) { return false; } Matcher m = PHONE_PATTERN.matcher(phone); return m.matches(); } } ``` ### 邮箱发送实现 发送邮箱参数: - `from:` 发件人, 例如:xxx@xx.com - `to`: 收件人, 例如:xxx@xx.com - `subject`: 主题, 例如:监控告警 - `body`: 内容体 **邮件传输协议** - **SMTP协议**:全称为 Simple Mail Transfer Protocol,**简单邮件传输协议**。它定义了邮件客户端软件和SMTP邮件服务器之间,以及两台SMTP邮件服务器之间的通信规则。 - **POP3协议**:全称为 Post Office Protocol,**邮局协议**。它定义了邮件客户端软件和POP3邮件服务器的通信 规则 - **IMAP协议**:全称为 Internet Message Access Protocol, **Internet消息访问协议**,它是**对POP3协议一种扩展**,也是定义了邮件客户端软件和IMAP邮件服务器的通信规则 增加依赖mail,业务服务`nla-user-serive` ```xml org.springframework.boot spring-boot-starter-mail ``` #### 配置 ```yml spring: #邮箱配置 mail: host: smtp.qq.com #发送邮件服务器 username: xxx@qq.com #发送邮件的邮箱地址 password: xx #客户端授权码,不是邮箱密码 from: xxx@qq.com # 发送邮件的地址,和上面username一致 properties.mail.smtp.starttls.enable: true properties.mail.smtp.starttls.required: true properties.mail.smtp.ssl.enable: true default-encoding: utf-8 ``` #### 发送邮件封装方法 ```java @Service @Slf4j public class MailServiceImpl implements MailService { /** * springboot 提供的一个发送邮件的简单抽象,直接注入即可 */ @Resource private JavaMailSender mailSender; @Value("${spring.mail.from}") private String from; /** * 发送邮件 * * @param to 收件人 * @param subject 主题 * @param content 内容 */ @Override public void sendMail(String to, String subject, String content) { //创建一个邮箱消息对象 SimpleMailMessage message = new SimpleMailMessage(); //配置邮箱发送人 message.setFrom(from); //邮件的收件人 message.setTo(to); //邮件的主题 message.setSubject(subject); //邮件的内容 message.setText(content); mailSender.send(message); log.info("邮件发送成功:{}", message); } } ``` ### 实现邮箱或者手机号发送 ```java @Resource private MailService mailService; @Autowired // 不要使用Resource,否则会报错 private StringRedisTemplate redisTemplate; @ApiOperation("邮件发送验证码") @GetMapping("send") public JsonData sendCode(String from, String to) { String cacheKey = String.format(from + "_%s", to); String cacheValue = redisTemplate.opsForValue().get(cacheKey); //如果不为空,则判断是否60秒内重复发送 if (StringUtils.isNotBlank(cacheValue)) { long ttl = Long.parseLong(cacheValue.split("_")[1]); //当前时间戳-验证码发送时间戳,如果小于60秒,则不给重复发送 if (System.currentTimeMillis() - ttl < 1000 * 60) { log.info("重复发送验证码,时间间隔:{} 秒", (System.currentTimeMillis() - ttl) / 1000); return JsonData.buildResult(BizCodeEnum.CODE_LIMITED); } } //拼接验证码: 验证码+时间戳: 2322_324243232424324 String code = CommonUtil.getRandomCode(6); String value = code + "_" + System.currentTimeMillis(); redisTemplate.opsForValue().set(cacheKey, value, CAPTCHA_CODE_EXPIRED, TimeUnit.MILLISECONDS); if (CheckUtil.isEmail(to)) { //邮箱验证码 mailService.sendMail(to, "电商验证码", code); log.info("发送邮箱验证码[{}]成功!", code); return JsonData.buildSuccess(); } else if (CheckUtil.isPhone(to)) { //短信验证码 log.info("要发送短信验证码[{}]", code); } return JsonData.buildResult(BizCodeEnum.CODE_TO_ERROR); } ```
评论区