SpringBoot整合邮件验证码、注册激活链接

大家肯定遇到过很多下面的场景,当你在某个网站、某个APP注册信息的时候,为了验证身份,需要填写你的邮箱地址,之后邮箱可能会收到验证码,也可能会收到一个里面有激活链接的邮件,叫你点击链接进行激活。

就像下面这样:

这两种方式是登录注册时对邮件任务的经典应用场景。

那么今天就继续上次整合登录的讲解,人脸识别和三方登录,往期的文章已经介绍了,有兴趣的回头再去看看。

邮件任务

SpringBoot已经为我们整合了对发送邮件的支持,也就是说我们可以使用代码,很方便的给指定的邮箱发送任意内容的邮件。

整合步骤

引入邮件任务依赖
<!--引入邮件任务依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>
在SpringBoot的配置文件中配置邮件信息(以QQ邮箱为例)
spring: 
  mail:
    username: 373675032@qq.com
    password: ****************
    host: smtp.qq.com
    port: 465
    properties:
      mail:
        smtp:
          auth: true
          starttls:
            enable: true
            required: true
          socketFactory:
            port: 465
            class: javax.net.ssl.SSLSocketFactory
            fallback: false
    valid: 5
    title: "用户验证"
    template: "您的动态验证码为:<strong style='color: red'>%s</strong>,%d分钟内有效,若非本人操作,请勿泄露。"

  • username:发送方邮箱,也就是你的邮箱地址
  • password:SMTP服务密码,并不是你的QQ密码
spring.mail.password通过下面方法获得

封装邮件服务类
/**
 * @author XUEW
 * @apiNote 邮件服务
 */
@Service
public class EmailService {

    @Autowired
    private JavaMailSenderImpl mailSender;

    /**
     * 发送方邮箱
     */
    @Value("${spring.mail.username}")
    private String email;

    /**
     * 有效时长
     */
    @Value("${spring.mail.valid}")
    private Integer valid;

    /**
     * 内容模版
     */
    @Value("${spring.mail.template}")
    private String template;

    /**
     * 标题
     */
    @Value("${spring.mail.title}")
    private String title;

    /**
     * 发送邮件验证码
     * @param targetEmail 目标邮箱
     * @return 验证码
     */
    public String sendEmailCode(String targetEmail) {
        // 生成随机验证码
        String verifyCode = RandomUtil.randomNumbers(6);
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage);
        try {
            helper.setSubject(title);
            helper.setText(String.format(template, verifyCode, valid), true);
            helper.setFrom(email);
            helper.setTo(targetEmail);
        } catch (MessagingException e) {
            e.printStackTrace();
        }
        mailSender.send(mimeMessage);
        return verifyCode;
    }
    
    /**
     * 发送邮箱
     * @param targetEmail 目标邮箱
     * @param content 发送内容
     */
    public void sendEmail(String targetEmail, String title, String content) {
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage);
        try {
            helper.setSubject(title);
            helper.setText(content, true);
            helper.setFrom(email);
            helper.setTo(targetEmail);
        } catch (MessagingException e) {
            e.printStackTrace();
        }
        mailSender.send(mimeMessage);
    }
}

邮件验证码的实现思路

操作步骤大家肯定都知道,输入完邮箱地址点击获取验证码后,按钮变成了disable状态,并且开始倒计时一分钟。

如此同时向后端发送异步请求,调用刚才写的邮件服务向输入的邮箱发送验证码。

/**
 * 发送邮箱验证码
 */
@PostMapping("/sendEmailCode")
@ResponseBody
public ResponseResult sendEmailCode(String email, Map<String,Object> map, HttpSession session) {
    if (StrUtil.isEmpty(email)) {
        return ResponseResult.failure(500, "邮箱不能为空");
    }
    // 发送验证码
    String verifyCode = emailService.sendEmailCode(email);
    map.put("email", email);
    map.put("code", verifyCode);
    map.put("time", new Date());
    session.setAttribute("EMAIL_CODE" + email, map);
    return ResponseResult.success();
}

发送完成后,将验证码与邮箱缓存,这里可以使用session或者redis。

用户收到验证码后,填写后点击登录,后端服务开始进行一些列的校验工作,比如:缓存中是否存在此邮箱?验证码是否正确?验证码是否过期?诸如此类的操作。校验没问题,那么就可以进行入库保存等操作了。

/**
 * 验证码登录
 */
@GetMapping("/loginEmail")
public String loginSms(String email, String code, HttpSession session) {
    Map<String,Object> codeData = (Map<String, Object>) session.getAttribute("EMAIL_CODE" + email);
    if (codeData == null) {
        throw new RuntimeException("登录失败:尚未发送验证码");
    }
    String sentCode = (String) codeData.get("code");
    Calendar calendar = Calendar.getInstance();
    calendar.setTime((Date) codeData.get("time"));
    calendar.add(Calendar.MINUTE, 5);
    if (System.currentTimeMillis() > calendar.getTime().getTime()) {
        session.removeAttribute("EMAIL_CODE" + email);
        throw new RuntimeException("登录失败:验证码已经超时");
    }
    if (!sentCode.equals(code)) {
        throw new RuntimeException("登录失败:验证码错误");
    }

    // 校验全部通过,执行入库等业务操作
    // ...
    
    return "redirect:/success";
}

邮件激活链接实现思路

那么激活链接是如何实现的呢?首先既然叫激活,那么说明用户的信息肯定是已经存入数据库了,只不过用户的状态是尚未激活,所以无法正常使用系统。那么点击激活链接,其实主要目的就是把用户的状态修改为正常,大家首先要明白这一点。

当用户注册完成后,默认状态为【未激活】,在入库之后,后端就需要给注册邮箱发送激活链接,那么激活链接如何设计呢?我给大家提供两个思路:

思路一

生成一串随机字符串叫做密钥,因为用户信息已经存库,所以用户ID我们也是可以获取到,之后将密钥和用户id缓存,同样可以使用session或者redis。

之后将密钥和用户id拼接成URL的参数,像下面这样:

http://localhost:8080/activate?uid=1&key=asjkdjflksdajflasdfjdlsfj
/**
 * 发送邮箱激活链接
 */
@PostMapping("/sendEmailActivateUrl")
@ResponseBody
public ResponseResult sendEmailActivateUrl(Integer uid, HttpSession session) {
    // 前提条件:发送之前已经保存用户到数据库
    User user = userService.get(uid);
    String serverUrl = "http://localhost:8080";
    String key = RandomUtil.randomString(10);
    String content = String.format("点击此链接进行激活: <a href=\"%s/activate?uid=%d&key=%s\">点我激活</a>", serverUrl, uid, key);

    // 发送邮件
    emailService.sendEmail(user.getEmail(), "激活链接", content);
    // 存入缓存
    session.setAttribute("ACTIVATE" + uid, key);
    return ResponseResult.success();
}

注意在激活链接中访问的是activate请求,路径传参数输入get方式,所以后端服务需要在controller中编写此服务用于激活用户。

/**
 * 用户激活
 * @param uid 用户ID
 * @param key 密钥
 * @return
 */
@GetMapping("/activate")
public String activate(Integer uid, String key, HttpSession session) {
    User user = userService.get(uid);
    // 开始一系列校验
    if (Assert.isEmpty(user) ) {
        throw new RuntimeException("激活失败:尚未注册的用户");
    }
    if (user.getStatus() == 1) {
        throw new RuntimeException("激活失败:用户已完成激活");
    }
    // 获取激活信息
    String activateKey = (String) session.getAttribute("ACTIVATE" + uid);
    if (Assert.isEmpty(activateKey)) {
        throw new RuntimeException("激活失败:尚未发送激活邮件");
    }
    if (!activateKey.equals(key)) {
        throw new RuntimeException("激活失败:密钥匹配失败");
    }

    // 校验通过,更新用户状态
    // ...

    return "redirect:/success";
}

发送邮件的内容就可以是下面这样:

点击此链接进行激活: <a href="http://localhost:8080/activate?uid=1&key=asjkdjflksdajflasdfjdlsfj">点我激活</a>

收到的邮件如下:

思路二

和上面的区别主要在激活链接这块的设计,上面的可以实现,但是安全性和时效性上并不是很灵活和安全,所以我更推荐大家使用JWT Token的方式去作为激活链接的加密参数。

Token里面可以用来存放一些数据,并且可以很方便的设置Token的过期时间,这样我们在做校验的时候也会方便很多了。

所谓的JWT Token,也还是一串字符串,但是是混乱的,像下面这样:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImNpbmNvIiwicm9sZSI6ImFkbWluIiwic3ViIjoiYWRtaW4tdGVzdCIsImV4cCI6MTY0MjE0MDUyMCwianRpIjoiNDc0ZWRhNjUtYzI0Ny00OTg1LTgwNGEtMjM5NjliZjFhNDRhIn0.cRDXDjBmCMfgLCxNPINh_KBeKDPG-VNSA2LY95ySsmc

项目开源:https://github.com/373675032/login-system

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值