SpringBoot 邮件找回密码功能实现

之前写了一篇文章 SpringBoot 发送邮件,本文介绍 SpringBoot 找回密码功能。

 

一、效果图

1、找回密码页面

http://localhost:8080/login?action=lostpass

SpringBoot 邮件找回密码功能实现

可以输入用户名或电子邮箱,两者在都是 unique 的字段

2、显示信息

http://localhost:8080/lostpass

如果用户名或邮箱不存在

SpringBoot 邮件找回密码功能实现

 

如果用户名或邮箱存在

SpringBoot 邮件找回密码功能实现

 

3、用户存在,接受邮件

打开qq邮箱,可以看到有一封邮件

SpringBoot 邮件找回密码功能实现

如果没收到,可以垃圾箱里有没有

 

打开邮件,可以看如下

SpringBoot 邮件找回密码功能实现

 

4、显示重置密码页面

http://localhost:8080/resetpass?sid=fea52cfa44c7c11d75e1194986c290fd&account=saysky

SpringBoot 邮件找回密码功能实现

 

具体代码如下

二、邮件发送基本功能实现

这里主要是介绍邮箱找回密码的功能实现,所以只贴出一些比较关键的代码

1、pom.xml

  1. <!--邮件-->
  2. <dependency>
  3.     <groupId>org.springframework.boot</groupId>
  4.     <artifactId>spring-boot-starter-mail</artifactId>
  5. </dependency>

 

2、application.properties

  1. #qq企业邮箱
  2. spring.mail.host=smtp.exmail.qq.com
  3. spring.mail.username=master@liuyanzhao.com
  4. spring.mail.password=密码
  5. spring.mail.properties.smtp.auth=true
  6. spring.mail.properties.smtp.starttls.enable=true
  7. spring.mail.properties.smtp.starttls.required=true
  8. spring.mail.properties.mail.smtp.ssl.enable=true

 

3、MailService.java 邮件发送

  1. package com.liuyanzhao.chuyun.service;
  2. import ch.qos.logback.classic.Logger;
  3. import org.slf4j.LoggerFactory;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.beans.factory.annotation.Value;
  6. import org.springframework.core.io.FileSystemResource;
  7. import org.springframework.mail.SimpleMailMessage;
  8. import org.springframework.mail.javamail.JavaMailSender;
  9. import org.springframework.mail.javamail.MimeMessageHelper;
  10. import org.springframework.stereotype.Service;
  11. import javax.mail.MessagingException;
  12. import javax.mail.internet.MimeMessage;
  13. import java.io.File;
  14. /**
  15.  * @author 言曌
  16.  * @date 2018/2/6 下午8:25
  17.  */
  18. @Service
  19. public class MailService {
  20.     private final Logger logger = (Logger) LoggerFactory.getLogger(this.getClass());
  21.     @Autowired
  22.     private JavaMailSender sender;
  23.     @Value("${spring.mail.username}")
  24.     private String from;
  25.     /**
  26.      * 发送纯文本的简单邮件
  27.      * @param to
  28.      * @param subject
  29.      * @param content
  30.      */
  31.     public void sendSimpleMail(String to, String subject, String content){
  32.         SimpleMailMessage message = new SimpleMailMessage();
  33.         message.setFrom(from);
  34.         message.setTo(to);
  35.         message.setSubject(subject);
  36.         message.setText(content);
  37.         try {
  38.             sender.send(message);
  39.             logger.info("简单邮件已经发送。");
  40.         } catch (Exception e) {
  41.             logger.error("发送简单邮件时发生异常!", e);
  42.         }
  43.     }
  44.     /**
  45.      * 发送html格式的邮件
  46.      * @param to
  47.      * @param subject
  48.      * @param content
  49.      */
  50.     public void sendHtmlMail(String to, String subject, String content){
  51.         MimeMessage message = sender.createMimeMessage();
  52.         try {
  53.             //true表示需要创建一个multipart message
  54.             MimeMessageHelper helper = new MimeMessageHelper(message, true);
  55.             helper.setFrom(from);
  56.             helper.setTo(to);
  57.             helper.setSubject(subject);
  58.             helper.setText(content, true);
  59.             sender.send(message);
  60.             logger.info("html邮件已经发送。");
  61.         } catch (MessagingException e) {
  62.             logger.error("发送html邮件时发生异常!", e);
  63.         }
  64.     }
  65.     /**
  66.      * 发送带附件的邮件
  67.      * @param to
  68.      * @param subject
  69.      * @param content
  70.      * @param filePath
  71.      */
  72.     public void sendAttachmentsMail(String to, String subject, String content, String filePath){
  73.         MimeMessage message = sender.createMimeMessage();
  74.         try {
  75.             //true表示需要创建一个multipart message
  76.             MimeMessageHelper helper = new MimeMessageHelper(message, true);
  77.             helper.setFrom(from);
  78.             helper.setTo(to);
  79.             helper.setSubject(subject);
  80.             helper.setText(content, true);
  81.             FileSystemResource file = new FileSystemResource(new File(filePath));
  82.             String fileName = filePath.substring(filePath.lastIndexOf(File.separator));
  83.             helper.addAttachment(fileName, file);
  84.             sender.send(message);
  85.             logger.info("带附件的邮件已经发送。");
  86.         } catch (MessagingException e) {
  87.             logger.error("发送带附件的邮件时发生异常!", e);
  88.         }
  89.     }
  90.     /**
  91.      * 发送嵌入静态资源(一般是图片)的邮件
  92.      * @param to
  93.      * @param subject
  94.      * @param content 邮件内容,需要包括一个静态资源的id,比如:<img src=\"cid:rscId01\" >
  95.      * @param rscPath 静态资源路径和文件名
  96.      * @param rscId 静态资源id
  97.      */
  98.     public void sendInlineResourceMail(String to, String subject, String content, String rscPath, String rscId){
  99.         MimeMessage message = sender.createMimeMessage();
  100.         try {
  101.             //true表示需要创建一个multipart message
  102.             MimeMessageHelper helper = new MimeMessageHelper(message, true);
  103.             helper.setFrom(from);
  104.             helper.setTo(to);
  105.             helper.setSubject(subject);
  106.             helper.setText(content, true);
  107.             FileSystemResource res = new FileSystemResource(new File(rscPath));
  108.             helper.addInline(rscId, res);
  109.             sender.send(message);
  110.             logger.info("嵌入静态资源的邮件已经发送。");
  111.         } catch (MessagingException e) {
  112.             logger.error("发送嵌入静态资源的邮件时发生异常!", e);
  113.         }
  114.     }
  115. }

 

三、找回密码核心实现

1、MailRetrieve

  1. package com.liuyanzhao.chuyun.entity;
  2. import javax.persistence.*;
  3. /**
  4.  * @author 言曌
  5.  * @date 2018/2/23 上午10:24
  6.  */
  7. @Entity
  8. @Table(name = "mail_retrieve")
  9. public class MailRetrieve{
  10.     private static final long serialVersionUID = -1L;
  11.     @Id
  12.     @GeneratedValue(strategy = GenerationType.AUTO)
  13.     private long id;
  14.     @Column(name = "account", nullable = true, length = 100)
  15.     private String account;
  16.     @Column(name = "sid", nullable = true, length = 255)
  17.     private String sid;
  18.     @Column(name = "out_time", nullable = true, length = 100)
  19.     private long outTime;
  20.     public MailRetrieve() {
  21.     }
  22.     public MailRetrieve(String account, String sid, long outTime) {
  23.         this.account = account;
  24.         this.sid = sid;
  25.         this.outTime = outTime;
  26.     }
  27.     public static long getSerialVersionUID() {
  28.         return serialVersionUID;
  29.     }
  30.     public long getId() {
  31.         return id;
  32.     }
  33.     public void setId(long id) {
  34.         this.id = id;
  35.     }
  36.     public MailRetrieve(String account) {
  37.         this.account = account;
  38.     }
  39.     public String getSid() {
  40.         return sid;
  41.     }
  42.     public void setSid(String sid) {
  43.         this.sid = sid;
  44.     }
  45.     public long getOutTime() {
  46.         return outTime;
  47.     }
  48.     public void setOutTime(long outTime) {
  49.         this.outTime = outTime;
  50.     }
  51. }

 

2、MailRetrieveRepository

  1. package com.liuyanzhao.chuyun.repository;
  2. import com.liuyanzhao.chuyun.entity.MailRetrieve;
  3. import org.springframework.data.jpa.repository.JpaRepository;
  4. /**
  5.  * @author 言曌
  6.  * @date 2018/2/23 上午10:40
  7.  */
  8. public interface MailRetrieveRepository extends JpaRepository<MailRetrieve, Long> {
  9.     /**
  10.      * 根据账号查找用户
  11.      *
  12.      * @param account
  13.      * @return
  14.      */
  15.     MailRetrieve findByAccount(String account);
  16. }

 

3、MailRetrieveService

  1. package com.liuyanzhao.chuyun.service;
  2. import com.liuyanzhao.chuyun.vo.ResultVO;
  3. /**
  4.  * 邮件找回密码Service
  5.  *
  6.  * @author 言曌
  7.  * @date 2018/2/23 上午10:27
  8.  */
  9. public interface MailRetrieveService {
  10.     /**
  11.      * 找回密码
  12.      *
  13.      * @param basePath
  14.      * @param account
  15.      * @return
  16.      */
  17.     public ResultVO generateMailUrl(String basePath, String account);
  18.     /**
  19.      * 邮件找回密码URL校验
  20.      * @param sid
  21.      * @param account
  22.      * @return
  23.      */
  24.     public ResultVO verifyMailUrl(String sid, String account);
  25. }

 

4、MailRetrieveServiceImpl

  1. package com.liuyanzhao.chuyun.service.impl;
  2. import com.liuyanzhao.chuyun.entity.MailRetrieve;
  3. import com.liuyanzhao.chuyun.entity.User;
  4. import com.liuyanzhao.chuyun.repository.MailRetrieveRepository;
  5. import com.liuyanzhao.chuyun.repository.UserRepository;
  6. import com.liuyanzhao.chuyun.service.MailRetrieveService;
  7. import com.liuyanzhao.chuyun.util.MD5Util;
  8. import com.liuyanzhao.chuyun.util.RandomUtil;
  9. import com.liuyanzhao.chuyun.vo.ResultVO;
  10. import com.sun.jmx.snmp.Timestamp;
  11. import org.springframework.beans.factory.annotation.Autowired;
  12. import org.springframework.stereotype.Service;
  13. /**
  14.  * @author 言曌
  15.  * @date 2018/2/23 上午10:30
  16.  */
  17. @Service
  18. public class MailRetrieveServiceImpl implements MailRetrieveService {
  19.     @Autowired
  20.     private UserRepository userRepository;
  21.     @Autowired
  22.     private MailRetrieveRepository mailRetrieveRepository;
  23.     /**
  24.      * 生成链接
  25.      * @param basePath
  26.      * @param account
  27.      * @return
  28.      */
  29.     @Override
  30.     public ResultVO generateMailUrl(String basePath, String account) {
  31.         User user = userRepository.findByUsername(account);
  32.         ResultVO resultVO = new ResultVO();
  33.         //用户名不存在
  34.         if (user == null) {
  35.             resultVO.setCode("001");
  36.             resultVO.setMsg("用户不存在");
  37.         } else {
  38.             //生成邮件URL唯一地址
  39.             String key = RandomUtil.getRandom(6) + "";
  40.             Timestamp outDate = new Timestamp(System.currentTimeMillis() + (long) (30 * 60 * 1000));//30分钟后过期
  41.             long outtimes = outDate.getSysUpTime();
  42.             String sid = account + "&" + key + "&" + outtimes;
  43.             MailRetrieve mailRetrieve = new MailRetrieve(account, MD5Util.encode(sid), outtimes);
  44.             MailRetrieve findMailRetrieve = mailRetrieveRepository.findByAccount(account);
  45.             if (findMailRetrieve != null) {
  46.                 mailRetrieveRepository.delete(findMailRetrieve);
  47.             }
  48.             mailRetrieveRepository.save(mailRetrieve);
  49.             resultVO.setCode("005");
  50.             resultVO.setMsg("邮件重置密码");
  51.             resultVO.setData(basePath + "resetpass?sid=" + MD5Util.encode(sid) + "&username=" + account);
  52.         }
  53.         return resultVO;
  54.     }
  55.     /**
  56.      * 验证链接
  57.      * @param sid
  58.      * @param username
  59.      * @return
  60.      */
  61.     @Override
  62.     public ResultVO verifyMailUrl(String sid, String username) {
  63.         ResultVO resultVO = new ResultVO();
  64.         MailRetrieve mailRetrieve = mailRetrieveRepository.findByAccount(username);
  65.         if(mailRetrieve != null) {
  66.             long outTime = mailRetrieve.getOutTime();
  67.             Timestamp outDate = new Timestamp(System.currentTimeMillis());
  68.             long nowTime = outDate.getSysUpTime();
  69.             System.out.println("nowTime:" + nowTime);
  70.             if (outTime <= nowTime) {
  71.                 resultVO.setCode("006");
  72.                 resultVO.setMsg("邮件已经过期!");
  73.             } else if ("".equals(sid)) {
  74.                 resultVO.setCode("007");
  75.                 resultVO.setMsg("sid不完整!");
  76.             } else if (!sid.equals(mailRetrieve.getSid())) {
  77.                 resultVO.setCode("008");
  78.                 resultVO.setMsg("sid错误!");
  79.             } else {
  80.                 resultVO.setCode("000");
  81.                 resultVO.setMsg("验证成功!");
  82.             }
  83.         } else {
  84.             //account 对应的用户不存在
  85.             resultVO.setCode("002");
  86.             resultVO.setMsg("链接无效!");
  87.         }
  88.         return resultVO;
  89.     }
  90. }

 

5、LoginController

  1. package com.liuyanzhao.chuyun.controller;
  2. import com.liuyanzhao.chuyun.entity.Authority;
  3. import com.liuyanzhao.chuyun.entity.User;
  4. import com.liuyanzhao.chuyun.repository.MailRetrieveRepository;
  5. import com.liuyanzhao.chuyun.repository.UserRepository;
  6. import com.liuyanzhao.chuyun.service.AuthorityService;
  7. import com.liuyanzhao.chuyun.service.MailRetrieveService;
  8. import com.liuyanzhao.chuyun.service.MailService;
  9. import com.liuyanzhao.chuyun.service.UserService;
  10. import com.liuyanzhao.chuyun.util.BCryptUtil;
  11. import com.liuyanzhao.chuyun.util.QQUtil;
  12. import com.liuyanzhao.chuyun.util.RegexUtil;
  13. import com.liuyanzhao.chuyun.vo.ResultVO;
  14. import org.springframework.beans.factory.annotation.Autowired;
  15. import org.springframework.stereotype.Controller;
  16. import org.springframework.web.bind.annotation.GetMapping;
  17. import org.springframework.web.bind.annotation.PostMapping;
  18. import org.springframework.web.bind.annotation.RequestParam;
  19. import org.springframework.web.servlet.ModelAndView;
  20. import javax.servlet.http.HttpServletRequest;
  21. /**
  22.  * @author 言曌
  23.  * @date 2018/2/23 下午5:31
  24.  */
  25. @Controller
  26. public class LoginController {
  27.     @Autowired
  28.     private UserRepository userRepository;
  29.     @Autowired
  30.     private UserService userService;
  31.     @Autowired
  32.     private MailRetrieveService mailRetrieveService;
  33.     @Autowired
  34.     private MailService mailService;
  35.     /**
  36.      * 登录页面显示
  37.      *
  38.      * @return
  39.      */
  40.     @GetMapping("/login")
  41.     public String login(@RequestParam(value = "action", required = false) String action) {
  42.         //登录
  43.         if (action == null) {
  44.             return "login";
  45.         }
  46.         //注册
  47.         else if ("register".equals(action)) {
  48.             return "forward:/register";
  49.         }
  50.         //忘记密码
  51.         else if ("lostpass".equals(action)) {
  52.             return "forward:/lostpass";
  53.         }
  54.         //设置密码
  55.         else if ("resetpass".equals(action)) {
  56.             return "forward:/resetpass";
  57.         }
  58.         //退出登录
  59.         else if ("logout".equals(action)) {
  60.             return "forward:/logout";
  61.         }
  62.         //其他
  63.         else {
  64.             return "login";
  65.         }
  66.     }
  67.     /**
  68.      * 忘记密码页面显示
  69.      *
  70.      * @return
  71.      */
  72.     @GetMapping(value = "/lostpass")
  73.     public ModelAndView forget() {
  74.         ModelAndView modelAndView = new ModelAndView("login");
  75.         modelAndView.addObject("action""lostpass");
  76.         modelAndView.addObject("message""请输入您的用户名或电子邮箱地址。您会收到一封包含创建新密码链接的电子邮件。");
  77.         return modelAndView;
  78.     }
  79.     /**
  80.      * 忘记密码提交,然后跳转登录页面
  81.      *
  82.      * @param request
  83.      * @param account
  84.      * @return
  85.      */
  86.     @PostMapping(value = "/lostpass")
  87.     public ModelAndView forgetUser(HttpServletRequest request, String account) {
  88.         ModelAndView modelAndView = new ModelAndView("login");
  89.         String username = "";
  90.         //1、获取用户名 username
  91.         if (RegexUtil.isEmail(account)) {
  92.             User user = userRepository.findByEmail(account);
  93.             if (user != null) {
  94.                 username = user.getUsername();
  95.             }
  96.         } else {
  97.             //account 是用户名
  98.             username = account;
  99.         }
  100.         User user = userRepository.findByUsername(username);
  101.         //2、判断用户是否存在
  102.         if(user != null) {
  103.             //用户存在
  104.             //2.1、生成链接
  105.             String path = request.getContextPath();
  106.             String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/";
  107.             ResultVO resultVO = mailRetrieveService.generateMailUrl(basePath, username);
  108.             String mailUrl = resultVO.getData().toString();
  109.             //2.2、发送邮件
  110.             String receiver = user.getEmail();//接收者
  111.             String subject = "[Spring Blog]密码重置";//邮件主题(标题)
  112.             StringBuilder content = new StringBuilder();
  113.             content.append("有人为以下账号请求了密码重置:<br/><br/>");
  114.             content.append("<a href=" + basePath + " target=\"_blank\">" + basePath + "</a><br/><br/>");
  115.             content.append("用户名:" + username + "<br/><br/>");
  116.             content.append("若这不是您本人要求的,请忽略本邮件,一切如常。<br/><br/>");
  117.             content.append("要重置您的密码,请打开下面的链接(如果邮箱对链接拦截,请复制链接在地址栏中打开,30分钟内有效):<br/><br/>");
  118.             content.append("<a href=" + mailUrl + " target=\"_blank\">" + mailUrl + "</a><br/><br/>");
  119.             mailService.sendHtmlMail(receiver, subject, content.toString());
  120.             //2.3、显示登录页面和提示信息
  121.             modelAndView.addObject("checkmail""请在您的电子邮箱中检查确认链接。");
  122.         } else {
  123.             //用户不存在
  124.             //2.1、 显示找回密码页面,和错误信息
  125.             modelAndView.addObject("error","用户名或邮箱不存在!");
  126.             modelAndView.addObject("action","lostpass");
  127.         }
  128.         return modelAndView;
  129.     }
  130.     /**
  131.      * 重置密码验证
  132.      * 验证通过,显示修改密码页面
  133.      *
  134.      * @param sid
  135.      * @param username
  136.      * @return
  137.      */
  138.     @GetMapping(value = "/resetpass")
  139.     public ModelAndView verifyMail(String sid, String username) {
  140.         ModelAndView modelAndView = new ModelAndView("login");
  141.         ResultVO resultVO = mailRetrieveService.verifyMailUrl(sid, username);
  142.         //验证通过,显示设置密码页面
  143.         if ("000".equals(resultVO.getCode())) {
  144.             modelAndView.addObject("action""resetpass");
  145.             modelAndView.addObject("username", username);
  146.             modelAndView.addObject("sid", sid);
  147.         } else {
  148.             //链接无效,显示找回密码页面
  149.             modelAndView.addObject("action""lostpass");
  150.             //显示错误信息
  151.             modelAndView.addObject("error", resultVO.getMsg());
  152.         }
  153.         return modelAndView;
  154.     }
  155.     /**
  156.      * 重置密码提交
  157.      *
  158.      * @param sid      根据用户sid来查询用户名
  159.      * @param password
  160.      * @return
  161.      */
  162.     @PostMapping(value = "/resetpass")
  163.     public ModelAndView resetPass(String sid, String username, String password) {
  164.         ModelAndView modelAndView = new ModelAndView("login");
  165.         ResultVO resultVO = mailRetrieveService.verifyMailUrl(sid, username);
  166.         //如果验证通过(防止用户自定义表单,再次验证)
  167.         if ("000".equals(resultVO.getCode())) {
  168.             //修改密码
  169.             User user = userService.getUserByUsername(username);
  170.             user.setPassword(BCryptUtil.encode(password));
  171.             userService.updateUser(user);
  172.         }
  173.         //验证失败
  174.         else {
  175.             //显示找回密码页面
  176.             modelAndView.addObject("action""lostpass");
  177.             //显示错误信息
  178.             modelAndView.addObject("error""用户名或sid不正确");
  179.         }
  180.         return modelAndView;
  181.     }
  182. }

删掉了多余的代码

 

6、login.html

找回密码

  1. <div id="lostpass-block" th:case="lostpass">
  2.         <p class="message" th:if="${message}" th:text="${message}"></p>
  3.         <div id="login_error" th:if="${error}" >
  4.             <span th:text="${error}"></span>请重新申请。
  5.         </div>
  6.         <form name="forgetForm" th:action="@{/lostpass}" id="forgetForm" method="post">
  7.             <p>
  8.                 <label for="account">用户名或电子邮件地址<br/>
  9.                     <input type="text" name="account" id="account" class="input" value="" size="20"/>
  10.                 </label>
  11.             </p>
  12.             <br>
  13.             <p class="submit">
  14.                 <input type="submit" value="获取新密码"/>
  15.             </p>
  16.         </form>
  17.     </div>

 

设置密码

  1. <div id="resetpass-block" th:case="resetpass">
  2.         <p class="message reset-pass">请在下方输入您的新密码。</p>
  3.         <div id="login_error" th:if="${message}" th:text="${message}"></div>
  4.         <form name="resetpassform" id="resetpassform" th:action="@{/resetpass}"
  5.               method="post" autocomplete="off">
  6.             <p>
  7.                 <label for="username"><strong>新密码</strong><br/>
  8.                     <input type="text" name="password" id="password" class="input" value="" size="20"/>
  9.                 </label>
  10.             </p>
  11.             <p class="description indicator-hint" style="margin-top: 10px;">正在为用户名为 <strong th:text="${username}"></strong> 的用户重置密码</p>
  12.             <br class="clear">
  13.             <input type="hidden" name="username" th:value="${username}">
  14.             <input type="hidden" name="sid" th:value="${sid}">
  15.             <p class="submit">
  16.                 <input type="submit" value="重置密码">
  17.             </p>
  18.         </form>
  19.     </div>

 

 

本文地址:https://liuyanzhao.com/7586.html

  • 微信
  • 交流学习,有偿服务
  • weinxin
  • 博客/Java交流群
  • 资源分享,问题解决,技术交流。群号:590480292
  • weinxin
言曌

发表评论

:?::razz::sad::evil::!::smile::oops::grin::eek::shock::???::cool::lol::mad::twisted::roll::wink::idea::arrow::neutral::cry::mrgreen: