简单实用的邮件任务 ,验证码、激活链接如何实现注册
大家肯定遇到过很多下面的场景,当你在某个网站、某个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