【SpringBoot】SpringBoot使用mail实现登录邮箱验证

CSDN 2024-07-16 17:35:02 阅读 86

427ef4152dbf4b6c92618a198935cb6c.png

  📝个人主页:哈__

期待您的关注 

1b7335aca73b41609b7f05d1d366f476.gif

目录

一、前期准备

1 开启邮箱服务

2 SpringBoot导入依赖

3 创建application.yml配置文件 

4 创建数据库文件

 5 配置redis服务

二、验证邮件发送功能 

三、注册功能实现邮箱验证

1 创建User实体类

2 创建UserParam

3 创建CaptchaService

4 创建EmailTemplateEnum

5 创建CaptchaServiceImpl

 6 创建CaptchaController

7 创建LoginController 

四、创建登陆页面 

1 login.html 

2 register.html


 

在实际的开发当中,不少的场景中需要我们使用更加安全的认证方式,同时也为了防止一些用户恶意注册,我们可能会需要用户使用一些可以证明个人身份的注册方式,如短信验证、邮箱验证等。

一、前期准备

为了实现邮箱认证服务,我们需要提供出来一个邮箱作为验证码的发送者,这里我使用的是QQ邮箱。

1 开启邮箱服务

首先打开QQ邮箱,然后找到设置,点击账号。

a5b0567d19f143a7acbdfa6c60ff09f0.png

然后找到下方的邮件协议服务,打开。因为这里我已经打开了,而且服务开启也较为简单,需要我们发送一个短信到指定的号码,可能会收取一定的费用,不过不是很多,好像是几毛钱。

31d8524967e54c389103fe92393c8b86.png

 开启之后会给你一个授权码,一定要记住这个授权码,发邮件的时候需要这个。

2 SpringBoot导入依赖

核心的就是mail依赖,因为我这个项目东西不少,为了方便我就全拷贝过来了,可能有的用不到。

<code> <properties>

<java.version>1.8</java.version>

<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

<spring-boot.version>2.7.6</spring-boot.version>

<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

<java.version>1.8</java.version>

<mybatisplus.version>3.4.3.1</mybatisplus.version>

<mybatisplus.generator>3.3.2</mybatisplus.generator>

<mybatisplus.velocity>1.7</mybatisplus.velocity>

</properties>

<dependencies>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-web</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-mail</artifactId>

</dependency>

<dependency>

<groupId>org.slf4j</groupId>

<artifactId>slf4j-log4j12</artifactId>

</dependency>

<dependency>

<groupId>org.apache.commons</groupId>

<artifactId>commons-lang3</artifactId>

</dependency>

<dependency>

<groupId>com.mysql</groupId>

<artifactId>mysql-connector-j</artifactId>

</dependency>

<dependency>

<groupId>org.projectlombok</groupId>

<artifactId>lombok</artifactId>

<optional>true</optional>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-test</artifactId>

<scope>test</scope>

</dependency>

<dependency>

<groupId>com.baomidou</groupId>

<artifactId>mybatis-plus-boot-starter</artifactId>

<version>${mybatisplus.version}</version>

</dependency>

<dependency>

<groupId>com.baomidou</groupId>

<artifactId>mybatis-plus-generator</artifactId>

<version>${mybatisplus.generator}</version>

</dependency>

<dependency>

<groupId>org.apache.velocity</groupId>

<artifactId>velocity</artifactId>

<version>${mybatisplus.velocity}</version>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-devtools</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-data-redis</artifactId>

</dependency>

<dependency>

<groupId>com.alibaba</groupId>

<artifactId>fastjson</artifactId>

<version>1.2.56</version>

</dependency>

</dependencies>

3 创建application.yml配置文件 

spring:

#邮件服务配置

mail:

host: smtp.qq.com #邮件服务器地址

protocol: smtp #协议

username: #发送邮件的邮箱也就是你开通服务的邮箱

password: #开通服务后得到的授权码

default-encoding: utf-8 #邮件内容的编码

redis:

host: 127.0.0.1

port: 6379

datasource:

driver-class-name: com.mysql.cj.jdbc.Driver

url: jdbc:mysql://localhost:3306/mail

username: root

password: # 数据库的密码

4 创建数据库文件

在上边的配置文件中你也看到了,我们用到了mysql还有redis。

DROP TABLE IF EXISTS `user`;

CREATE TABLE `user` (

`id` int(11) NOT NULL AUTO_INCREMENT,

`account` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,

`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,

PRIMARY KEY (`id`) USING BTREE

) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Compact;

-- ----------------------------

-- Records of user

-- ----------------------------

INSERT INTO `user` VALUES (1, 'admin', '123456');

INSERT INTO `user` VALUES (2, '123456', '123456');

SET FOREIGN_KEY_CHECKS = 1;

 5 配置redis服务

在之前的文章当中我有说到过安装redis的事情,大家可以看看我这篇文章。【Spring】SpringBoot整合Redis,用Redis实现限流(附Redis解压包)_springboot 限流 redis-CSDN博客

二、验证邮件发送功能 

大家可以先看一下我的项目结构。其中的一些代码是我学习另一位大佬的文章,这篇文章也是对该大佬文章的一个总结。蒾酒-CSDN博客

d18a1b016999488794cb4594e46ab934.png

我们最重要的邮件发送工具 就是util包下的EmailApi。将以下代码导入后,创建一个测试方法。

<code>import lombok.SneakyThrows;

import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.core.io.FileSystemResource;

import org.springframework.mail.SimpleMailMessage;

import org.springframework.mail.javamail.JavaMailSender;

import org.springframework.mail.javamail.MimeMessageHelper;

import org.springframework.stereotype.Component;

import javax.annotation.Resource;

import javax.mail.internet.MimeMessage;

import java.io.File;

import java.util.Objects;

/**

* @author mijiupro

*/

@Component

@Slf4j

public class EmailApi {

@Resource

private JavaMailSender mailSender;

@Value("${spring.mail.username}")

private String from ;// 发件人

/**

* 发送纯文本的邮件

* @param to 收件人

* @param subject 主题

* @param content 内容

* @return 是否成功

*/

@SneakyThrows(Exception.class)

public boolean sendGeneralEmail(String subject, String content, String... to){

// 创建邮件消息

SimpleMailMessage message = new SimpleMailMessage();

message.setFrom(from);

// 设置收件人

message.setTo(to);

// 设置邮件主题

message.setSubject(subject);

// 设置邮件内容

message.setText(content);

// 发送邮件

mailSender.send(message);

return true;

}

/**

* 发送html的邮件

* @param to 收件人

* @param subject 主题

* @param content 内容

* @return 是否成功

*/

@SneakyThrows(Exception.class)

public boolean sendHtmlEmail(String subject, String content, String... to){

// 创建邮件消息

MimeMessage mimeMessage = mailSender.createMimeMessage();

MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);

helper.setFrom(from);

// 设置收件人

helper.setTo(to);

// 设置邮件主题

helper.setSubject(subject);

// 设置邮件内容

helper.setText(content, true);

// 发送邮件

mailSender.send(mimeMessage);

log.info("发送邮件成功");

return true;

}

/**

* 发送带附件的邮件

* @param to 收件人

* @param subject 主题

* @param content 内容

* @param filePaths 附件路径

* @return 是否成功

*/

@SneakyThrows(Exception.class)

public boolean sendAttachmentsEmail(String subject, String content, String[] to, String[] filePaths) {

// 创建邮件消息

MimeMessage mimeMessage = mailSender.createMimeMessage();

MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);

helper.setFrom(from);

// 设置收件人

helper.setTo(to);

// 设置邮件主题

helper.setSubject(subject);

// 设置邮件内容

helper.setText(content,true);

// 添加附件

if (filePaths != null) {

for (String filePath : filePaths) {

FileSystemResource file = new FileSystemResource(new File(filePath));

helper.addAttachment(Objects.requireNonNull(file.getFilename()), file);

}

}

// 发送邮件

mailSender.send(mimeMessage);

return true;

}

/**

* 发送带静态资源的邮件

* @param to 收件人

* @param subject 主题

* @param content 内容

* @param rscPath 静态资源路径

* @param rscId 静态资源id

* @return 是否成功

*/

@SneakyThrows(Exception.class)

public boolean sendInlineResourceEmail(String subject, String content, String to, String rscPath, String rscId) {

// 创建邮件消息

MimeMessage mimeMessage = mailSender.createMimeMessage();

MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);

// 设置发件人

helper.setFrom(from);

// 设置收件人

helper.setTo(to);

// 设置邮件主题

helper.setSubject(subject);

//html内容图片

String contentHtml = "<html><body>这是邮件的内容,包含一个图片:<img src=\'cid:" + rscId + "\'>"+content+"</body></html>";

helper.setText(contentHtml, true);

//指定讲资源地址

FileSystemResource res = new FileSystemResource(new File(rscPath));

helper.addInline(rscId, res);

mailSender.send(mimeMessage);

return true;

}

}

 进行邮件发送的测试。

cce23808aa9c476eb51c0896c3ef38fa.png

 看看结果,成功的发过来了。

e582910586124e76b8b1baf429574f72.png

 接下来就要进行登录注册功能的开发了。

三、注册功能实现邮箱验证

1 创建User实体类

<code>@Data

@EqualsAndHashCode(callSuper = false)

public class User implements Serializable {

private static final long serialVersionUID = 1L;

@TableId(value = "id", type = IdType.AUTO)

private Integer id;

private String account;

private String password;

}

2 创建UserParam

@Data

@EqualsAndHashCode(callSuper = false)

public class UserParam implements Serializable {

private static final long serialVersionUID = 1L;

@TableId(value = "id", type = IdType.AUTO)

private Integer id;

private String account;

private String password;

private String emailCode;

private String email;

}

3 创建CaptchaService

public interface CaptchaService {

boolean sendCaptcha(String email);

}

4 创建EmailTemplateEnum

这一步我没有选择创建CaptchaServiceImpl,因为这个类中涉及到了一些核心的代码,而我们一些类还没有创建完,我们先创建这样的一个枚举类,这个枚举类的作用就是定义我们发送邮件的一个模板,我们在发送邮件的时候,直接向模板内插入内容就可以了。

ublic enum EmailTemplateEnum {

// 验证码邮件

VERIFICATION_CODE_EMAIL_HTML("<html><body>用户你好,你的验证码是:<h1>%s</h1>请在五分钟内完成注册</body></html>","登录验证"),

// 用户被封禁邮件通知

USER_BANNED_EMAIL("用户你好,你已经被管理员封禁,封禁原因:%s", "封禁通知");

private final String template;

private final String subject;

EmailTemplateEnum(String template, String subject) {

this.template = template;

this.subject = subject;

}

public String getTemplate(){

return this.template;

}

public String set(String captcha) {

return String.format(this.template, captcha);

}

public String getSubject() {

return this.subject;

}

}

5 创建CaptchaServiceImpl

首先把我们前端传过来的邮箱加一个前缀,用于redis中的存储,因为我们不仅可以有邮箱认证还可以有手机认证。

@Resource

StringRedisTemplate stringRedisTemplate;

@Resource

EmailApi emailApi;

@Override

public boolean sendCaptcha(String email) {

sendMailCaptcha("login:email:captcha:"+email);

return true;

}

 在redis当中,验证码的存储使用的是Hash结构,Hash存储了验证码,验证次数,还有上一次的发送时间,因为我们要限制一分钟发送的次数。一分钟内我们只能发一条短信,验证码在redis中的过期时间为五分钟,在验证码未过期之前发送的认证,都会让这个发送次数加一,倘若发送的次数达到了5次还要发送,那么就封禁一天不让发送短信。

例如,在3:30:30的时候发送了一次短信,一分钟后,3:31:30的时候又发送了短信,直到3:35:30的时候又发了一次,此时的发送次数已经达到了5,这时候就会封一天,因为每次发送验证码的时候,redis都存储着上一次还没过期的验证码,所以发送次数会增加。

下边讲一下代码。

先从redis中找到这个hash结构,如果hash结构的值不为空并且达到了发送次数上限,就封禁一天,否则的话看一下上一次的发送时间是否存在,如果存在的话,判断一下当前时间和上一次的发送时间间隔是否大于60秒,如果小于60秒那么不让发送。如果正常发送短信,那么就把发送的次数加1,然后用随机数生成一个六位数的验证码,发送验证码,并且向redis中保存刚才的hash结构。

private boolean sendMailCaptcha(String key){

BoundHashOperations<String, String, String> hashOps = stringRedisTemplate.boundHashOps(key);

// 初始检查

String lastSendTimestamp = hashOps.get("lastSendTimestamp");

String sendCount = hashOps.get("sendCount");

if(StringUtils.isNotBlank(sendCount)&&Integer.parseInt(sendCount)>=5){

hashOps.expire(24, TimeUnit.HOURS);

throw new RuntimeException("验证码发送过于频繁");

}

if(StringUtils.isNotBlank(lastSendTimestamp)){

long lastSendTime = Long.parseLong(lastSendTimestamp);

long currentTime = System.currentTimeMillis();

long elapsedTime = currentTime - lastSendTime;

if(elapsedTime < 60 * 1000){

throw new RuntimeException("验证码发送过于频繁");

}

}

int newSendCount = StringUtils.isNotBlank(sendCount) ? Integer.parseInt(sendCount) + 1 : 1;

String captcha = RandomStringUtils.randomNumeric(6);

try {

sendCaptcha(key,captcha);

} catch (Exception e) {

return false;

}

hashOps.put("captcha", captcha);

hashOps.put("lastSendTimestamp", String.valueOf(System.currentTimeMillis()));

hashOps.put("sendCount", String.valueOf(newSendCount));

hashOps.expire(5, TimeUnit.MINUTES); // 设置过期时间为5分钟

return true;

}

发送验证码调用的是下边的函数。

private void sendCaptcha(String hashKey, String captcha) thorw Exception{

// 根据hashKey判断是发送邮件还是短信,然后调用相应的发送方法

if("email".equals(hashKey.split(":")[1])){

if (!emailApi.sendHtmlEmail(EmailTemplateEnum.VERIFICATION_CODE_EMAIL_HTML.getSubject(),

EmailTemplateEnum.VERIFICATION_CODE_EMAIL_HTML.set(captcha),hashKey.split(":")[3])) {

throw new RuntimeException("发送邮件失败");

}

}

}

 6 创建CaptchaController

@RestController

@RequestMapping("/captcha")

public class CaptchaController {

@Resource

CaptchaService captchaService;

@Resource

EmailApi emailApi;

@RequestMapping("/getCaptcha")

public Result sendCaptcha(String email){

boolean res = captchaService.sendCaptcha(email);

if(res){

return new Result("发送成功",200,null);

}

return new Result("发送失败",500,null);

}

}

7 创建LoginController 

UserSevice的东西都很简单,都是mybatisplus的内容,如果不太了解可以看我这篇文章【Spring】SpringBoot整合MybatisPlus的基本应用_简单的springboot+mybatisplus的应用程序-CSDN博客

我这里并没有用UserService封装认证的过程,直接写到controller中了,大家能看懂就好。仅供学习使用。 

@Controller

public class LoginController {

@Autowired

UserService userService;

@Autowired

StringRedisTemplate stringRedisTemplate;

@GetMapping("/")

public String Login(){

return "redirect:/pages/login.html";

}

@RequestMapping("/register")

@ResponseBody

public Result registerUser( UserParam user ){

String email = user.getEmail();

String emailCode = user.getEmailCode();

BoundHashOperations<String, String, String> hashOps = stringRedisTemplate.boundHashOps("login:email:captcha:"+email);

String code = hashOps.get("captcha");

if(!Objects.equals(code, emailCode)){

return new Result("验证码错误",400,null);

}

User user1 = userService.getOne(new LambdaQueryWrapper<User>()

.eq(User::getAccount,user.getAccount()));

//如果有这个用户的信息要拒绝注册

if(user1!=null){

return new Result("",100,null);

}

User saveUser = new User();

BeanUtils.copyProperties(user,saveUser);

System.out.println(user);

System.out.println(saveUser);

boolean save = userService.save(saveUser);

if(!save){

return new Result("注册失败",300,null);

}

return new Result("注册成功",200,null);

}

}

到此为止,验证码的注册功能就已经实现完成了。

我们现在要做一个前端页面。

四、创建登陆页面 

在resources目录下创建static文件夹。在pages目录下添加login.html和register.html。至于jquery呢就要大家自己去找了。

6a71f75baaa54fae8b7c0bd5ab3e8d86.png

 

1 login.html 

<code><!DOCTYPE html>

<html lang="en">code>

<head>

<meta charset="UTF-8">code>

<meta name="viewport" content="width=device-width, initial-scale=1.0">code>

<title>Document</title>

<script src="../js/jquery.min.js"></script>code>

<script>

$(function() {

$(".btn").on("click", function () {

var account = $(".account").val()

var password = $(".password").val()

console.log(account)

console.log(password)

$.ajax({

type: "get",

url: "/login?&account=" + account + "&password=" + password,

success: function (data) {

if (data.code === 200) {

window.location.href = "../pages/index.html"code>

}else{

alert("登陆失败,请检查账号或者密码")

}

}

})

})

});

</script>

<style>

*{

padding: 0;

margin: 0px;

text-decoration: none;

}

body {

background-image: url(../images2/1.jpg);

background-size:cover;

background-attachment:fixed;

}

.loginbox {

height: 280px;

width: 26%;

margin: 340px auto;

background-color: rgb(158, 151, 158);

opacity: 0.7;

text-align: center;

}

.loginbox .top {

display: block;

height: 42px;

background: linear-gradient(90deg,pink,red,pink,yellow,red);

padding-top: 15px;

font-size: 20px;

font-weight: 600;

color: black;

}

.loginbox span {

color: black;

font-weight: 500;

font-size: 18px;

}

.loginbox input[type=text],

.loginbox input[type=password] {

height: 20px;

border: none;

border-radius: 4px;

outline: none;

}

.loginbox input[type=button] {

width: 40%;

height: 24px;

border: none;

border-radius: 4px;

margin: 0 auto;

outline: none;

}

.loginbox .a {

display: flex;

margin: 20px auto;

width: 45%;

justify-content: space-between;

}

.loginbox .a a {

color: white;

}

.loginbox .a a:hover {

color: rgb(228, 221, 228);

}

</style>

</head>

<body>

<div class="loginbox">code>

<span class="top">邮箱简易发送系统</span><br>code>

<span>账号:</span>

<input type="text" placeholder="请输入账号" class="account"><br><br>code>

<span>密码:</span>

<input type="password" placeholder="请输入密码" class="password"><br><br>code>

<input type="button" value="登录" class="btn">code>

<div class="a">code>

<a href="register.html">注册</a>code>

<a href="">忘记密码</a>code>

</div>

</div>

</body>

</html>

2 register.html

<!DOCTYPE html>

<html lang="en">code>

<head>

<meta charset="UTF-8">code>

<meta name="viewport" content="width=device-width, initial-scale=1.0">code>

<title>Document</title>

<link rel="stylesheet" href="../css/register.css">code>

<script src="../js/jquery.min.js"></script>code>

<script>

$(function(){

// 注册按钮点击事件

$(".btn").on("click",function(){

var account = $(".account").val().trim();

var password = $(".password").val().trim();

var emailCode = $(".emailCode").val().trim();

var email = $(".email").val().trim();

$.ajax({

type:"post",

url:"/register",

data:{"account":account,"password":password,"emailCode":emailCode,"email":email},

success:function(data){

if(data.code=== 100){

alert("该账号已存在");

}else if(data.code === 300){

alert("账号注册失败");

}else if(data.code === 400){

alert("验证码错误");

}else{

alert("账号注册成功");

window.location.href="../pages/login.html";code>

}

}

});

});

// 发送验证码按钮点击事件

$(".sendCode").on("click", function(){

var email = $(".email").val().trim();

if(email === "") {

alert("请输入邮箱");

return;

}

// 发送验证码请求

$.ajax({

type:"get",

url:"/captcha/getCaptcha?email="+email,

success:function(data){

console.log(data)

if(data.code === 200){

alert("验证码已发送到邮箱");code>

// 启动倒计时

startCountdown(60);

} else {

alert("验证码发送失败,请重试");

}

}

});

});

// 启动倒计时函数

function startCountdown(seconds) {

var timer = seconds;

$(".sendCode").prop("disabled", true);

var countdown = setInterval(function() {

$(".sendCode").val(timer + "秒后可重新发送");

timer--;

if (timer < 0) {

clearInterval(countdown);

$(".sendCode").prop("disabled", false);

$(".sendCode").val("发送验证码");

}

}, 1000);

}

});

</script>

<style>

*{

padding: 0;

margin: 0px;

text-decoration: none;

}

body {

background-image: url(../images2/1.jpg);

background-size: cover;

background-attachment: fixed;

}

.loginbox {

height: 380px; /* 调整高度以适应新字段 */

width: 26%;

margin: 340px auto;

background-color: rgb(158, 151, 158);

opacity: 0.7;

text-align: center;

}

.loginbox .top {

display: block;

height: 42px;

background: linear-gradient(90deg, pink, red, pink, yellow, red);

padding-top: 15px;

font-size: 20px;

font-weight: 600;

color: black;

}

.loginbox span {

color: black;

font-weight: 500;

font-size: 18px;

}

.loginbox input[type=text],

.loginbox input[type=password] {

height: 20px;

border: none;

border-radius: 4px;

outline: none;

}

.loginbox input[type=button] {

width: 40%;

height: 24px;

border: none;

border-radius: 4px;

margin: 0 auto;

outline: none;

}

.loginbox .a {

display: flex;

margin: 20px auto;

width: 45%;

justify-content: space-between;

}

.loginbox .a a {

color: white;

}

.loginbox .a a:hover {

color: rgb(228, 221, 228);

}

</style>

</head>

<body>

<div class="loginbox">code>

<span class="top">邮箱简易发送系统</span><br>code>

<span>账号:</span>

<input type="text" placeholder="请输入账号" class="account"><br><br>code>

<span>密码:</span>

<input type="password" placeholder="请输入密码" class="password"><br><br>code>

<span>邮箱:</span>

<input type="text" placeholder="请输入邮箱" class="email"><br><br>code>

<input type="button" value="发送验证码" class="sendCode"><br><br>code>

<span>验证码:</span>

<input type="text" placeholder="请输入验证码" class="emailCode"><br><br>code>

<input type="button" value="注册" class="btn">code>

<div class="a">code>

<a href="login.html">返回</a>code>

</div>

</div>

</body>

</html>

2625f728ad30493391e9248fc41e6579.png

 



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。