SpringBoot如何使用mail實(shí)現(xiàn)登錄郵箱驗(yàn)證
在實(shí)際的開發(fā)當(dāng)中,不少的場(chǎng)景中需要我們使用更加安全的認(rèn)證方式,同時(shí)也為了防止一些用戶惡意注冊(cè),我們可能會(huì)需要用戶使用一些可以證明個(gè)人身份的注冊(cè)方式,如短信驗(yàn)證、郵箱驗(yàn)證等。
一、前期準(zhǔn)備
為了實(shí)現(xiàn)郵箱認(rèn)證服務(wù),我們需要提供出來(lái)一個(gè)郵箱作為驗(yàn)證碼的發(fā)送者,這里我使用的是QQ郵箱。
1 開啟郵箱服務(wù)
首先打開QQ郵箱,然后找到設(shè)置,點(diǎn)擊賬號(hào)。
然后找到下方的郵件協(xié)議服務(wù),打開。因?yàn)檫@里我已經(jīng)打開了,而且服務(wù)開啟也較為簡(jiǎn)單,需要我們發(fā)送一個(gè)短信到指定的號(hào)碼,可能會(huì)收取一定的費(fèi)用,不過(guò)不是很多,好像是幾毛錢。
開啟之后會(huì)給你一個(gè)授權(quán)碼,一定要記住這個(gè)授權(quán)碼,發(fā)郵件的時(shí)候需要這個(gè)。
2 SpringBoot導(dǎo)入依賴
核心的就是mail依賴,因?yàn)槲疫@個(gè)項(xiàng)目東西不少,為了方便我就全拷貝過(guò)來(lái)了,可能有的用不到。
<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 創(chuàng)建application.yml配置文件
spring: #郵件服務(wù)配置 mail: host: smtp.qq.com #郵件服務(wù)器地址 protocol: smtp #協(xié)議 username: #發(fā)送郵件的郵箱也就是你開通服務(wù)的郵箱 password: #開通服務(wù)后得到的授權(quán)碼 default-encoding: utf-8 #郵件內(nèi)容的編碼 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: # 數(shù)據(jù)庫(kù)的密碼
4 創(chuàng)建數(shù)據(jù)庫(kù)文件
在上邊的配置文件中你也看到了,我們用到了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服務(wù)
在之前的文章當(dāng)中我有說(shuō)到過(guò)安裝redis的事情,大家可以看看我這篇文章。【Spring】SpringBoot整合Redis,用Redis實(shí)現(xiàn)限流(附Redis解壓包)_springboot 限流
二、驗(yàn)證郵件發(fā)送功能
大家可以先看一下我的項(xiàng)目結(jié)構(gòu)。其中的一些代碼是我參考學(xué)習(xí)另一位大佬的文章,這篇文章也是對(duì)該大佬文章的一個(gè)總結(jié)。參考資料:蒾酒
我們最重要的郵件發(fā)送工具 就是util包下的EmailApi。將以下代碼導(dǎo)入后,創(chuàng)建一個(gè)測(cè)試方法。
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 ;// 發(fā)件人 /** * 發(fā)送純文本的郵件 * @param to 收件人 * @param subject 主題 * @param content 內(nèi)容 * @return 是否成功 */ @SneakyThrows(Exception.class) public boolean sendGeneralEmail(String subject, String content, String... to){ // 創(chuàng)建郵件消息 SimpleMailMessage message = new SimpleMailMessage(); message.setFrom(from); // 設(shè)置收件人 message.setTo(to); // 設(shè)置郵件主題 message.setSubject(subject); // 設(shè)置郵件內(nèi)容 message.setText(content); // 發(fā)送郵件 mailSender.send(message); return true; } /** * 發(fā)送html的郵件 * @param to 收件人 * @param subject 主題 * @param content 內(nèi)容 * @return 是否成功 */ @SneakyThrows(Exception.class) public boolean sendHtmlEmail(String subject, String content, String... to){ // 創(chuàng)建郵件消息 MimeMessage mimeMessage = mailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true); helper.setFrom(from); // 設(shè)置收件人 helper.setTo(to); // 設(shè)置郵件主題 helper.setSubject(subject); // 設(shè)置郵件內(nèi)容 helper.setText(content, true); // 發(fā)送郵件 mailSender.send(mimeMessage); log.info("發(fā)送郵件成功"); return true; } /** * 發(fā)送帶附件的郵件 * @param to 收件人 * @param subject 主題 * @param content 內(nèi)容 * @param filePaths 附件路徑 * @return 是否成功 */ @SneakyThrows(Exception.class) public boolean sendAttachmentsEmail(String subject, String content, String[] to, String[] filePaths) { // 創(chuàng)建郵件消息 MimeMessage mimeMessage = mailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true); helper.setFrom(from); // 設(shè)置收件人 helper.setTo(to); // 設(shè)置郵件主題 helper.setSubject(subject); // 設(shè)置郵件內(nèi)容 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); } } // 發(fā)送郵件 mailSender.send(mimeMessage); return true; } /** * 發(fā)送帶靜態(tài)資源的郵件 * @param to 收件人 * @param subject 主題 * @param content 內(nèi)容 * @param rscPath 靜態(tài)資源路徑 * @param rscId 靜態(tài)資源id * @return 是否成功 */ @SneakyThrows(Exception.class) public boolean sendInlineResourceEmail(String subject, String content, String to, String rscPath, String rscId) { // 創(chuàng)建郵件消息 MimeMessage mimeMessage = mailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true); // 設(shè)置發(fā)件人 helper.setFrom(from); // 設(shè)置收件人 helper.setTo(to); // 設(shè)置郵件主題 helper.setSubject(subject); //html內(nèi)容圖片 String contentHtml = "<html><body>這是郵件的內(nèi)容,包含一個(gè)圖片:<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; } }
進(jìn)行郵件發(fā)送的測(cè)試。
看看結(jié)果,成功的發(fā)過(guò)來(lái)了。
接下來(lái)就要進(jìn)行登錄注冊(cè)功能的開發(fā)了。
三、注冊(cè)功能實(shí)現(xiàn)郵箱驗(yàn)證
1 創(chuàng)建User實(shí)體類
@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 創(chuàng)建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 創(chuàng)建CaptchaService
public interface CaptchaService { boolean sendCaptcha(String email); }
4 創(chuàng)建EmailTemplateEnum
這一步我沒有選擇創(chuàng)建CaptchaServiceImpl,因?yàn)檫@個(gè)類中涉及到了一些核心的代碼,而我們一些類還沒有創(chuàng)建完,我們先創(chuàng)建這樣的一個(gè)枚舉類,這個(gè)枚舉類的作用就是定義我們發(fā)送郵件的一個(gè)模板,我們?cè)诎l(fā)送郵件的時(shí)候,直接向模板內(nèi)插入內(nèi)容就可以了。
ublic enum EmailTemplateEnum { // 驗(yàn)證碼郵件 VERIFICATION_CODE_EMAIL_HTML("<html><body>用戶你好,你的驗(yàn)證碼是:<h1>%s</h1>請(qǐng)?jiān)谖宸昼妰?nèi)完成注冊(cè)</body></html>","登錄驗(yàn)證"), // 用戶被封禁郵件通知 USER_BANNED_EMAIL("用戶你好,你已經(jīng)被管理員封禁,封禁原因:%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 創(chuàng)建CaptchaServiceImpl
首先把我們前端傳過(guò)來(lái)的郵箱加一個(gè)前綴,用于redis中的存儲(chǔ),因?yàn)槲覀儾粌H可以有郵箱認(rèn)證還可以有手機(jī)認(rèn)證。
@Resource StringRedisTemplate stringRedisTemplate; @Resource EmailApi emailApi; @Override public boolean sendCaptcha(String email) { sendMailCaptcha("login:email:captcha:"+email); return true; }
在redis當(dāng)中,驗(yàn)證碼的存儲(chǔ)使用的是Hash結(jié)構(gòu),Hash存儲(chǔ)了驗(yàn)證碼,驗(yàn)證次數(shù),還有上一次的發(fā)送時(shí)間,因?yàn)槲覀円拗埔环昼姲l(fā)送的次數(shù)。一分鐘內(nèi)我們只能發(fā)一條短信,驗(yàn)證碼在redis中的過(guò)期時(shí)間為五分鐘,在驗(yàn)證碼未過(guò)期之前發(fā)送的認(rèn)證,都會(huì)讓這個(gè)發(fā)送次數(shù)加一,倘若發(fā)送的次數(shù)達(dá)到了5次還要發(fā)送,那么就封禁一天不讓發(fā)送短信。
例如,在3:30:30的時(shí)候發(fā)送了一次短信,一分鐘后,3:31:30的時(shí)候又發(fā)送了短信,直到3:35:30的時(shí)候又發(fā)了一次,此時(shí)的發(fā)送次數(shù)已經(jīng)達(dá)到了5,這時(shí)候就會(huì)封一天,因?yàn)槊看伟l(fā)送驗(yàn)證碼的時(shí)候,redis都存儲(chǔ)著上一次還沒過(guò)期的驗(yàn)證碼,所以發(fā)送次數(shù)會(huì)增加。
下邊講一下代碼。
先從redis中找到這個(gè)hash結(jié)構(gòu),如果hash結(jié)構(gòu)的值不為空并且達(dá)到了發(fā)送次數(shù)上限,就封禁一天,否則的話看一下上一次的發(fā)送時(shí)間是否存在,如果存在的話,判斷一下當(dāng)前時(shí)間和上一次的發(fā)送時(shí)間間隔是否大于60秒,如果小于60秒那么不讓發(fā)送。如果正常發(fā)送短信,那么就把發(fā)送的次數(shù)加1,然后用隨機(jī)數(shù)生成一個(gè)六位數(shù)的驗(yàn)證碼,發(fā)送驗(yàn)證碼,并且向redis中保存剛才的hash結(jié)構(gòu)。
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("驗(yàn)證碼發(fā)送過(guò)于頻繁"); } if(StringUtils.isNotBlank(lastSendTimestamp)){ long lastSendTime = Long.parseLong(lastSendTimestamp); long currentTime = System.currentTimeMillis(); long elapsedTime = currentTime - lastSendTime; if(elapsedTime < 60 * 1000){ throw new RuntimeException("驗(yàn)證碼發(fā)送過(guò)于頻繁"); } } 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); // 設(shè)置過(guò)期時(shí)間為5分鐘 return true; }
發(fā)送驗(yàn)證碼調(diào)用的是下邊的函數(shù)。
private void sendCaptcha(String hashKey, String captcha) thorw Exception{ // 根據(jù)hashKey判斷是發(fā)送郵件還是短信,然后調(diào)用相應(yīng)的發(fā)送方法 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("發(fā)送郵件失敗"); } } }
6 創(chuàng)建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("發(fā)送成功",200,null); } return new Result("發(fā)送失敗",500,null); } }
7 創(chuàng)建LoginController
UserSevice的東西都很簡(jiǎn)單,都是mybatisplus的內(nèi)容,如果不太了解可以看我這篇文章【Spring】SpringBoot整合MybatisPlus的基本應(yīng)用_簡(jiǎn)單的springboot+mybatisplus的應(yīng)用程序
我這里并沒有用UserService封裝認(rèn)證的過(guò)程,直接寫到controller中了,大家能看懂就好。僅供學(xué)習(xí)使用。
@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("驗(yàn)證碼錯(cuò)誤",400,null); } User user1 = userService.getOne(new LambdaQueryWrapper<User>() .eq(User::getAccount,user.getAccount())); //如果有這個(gè)用戶的信息要拒絕注冊(cè) 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("注冊(cè)失敗",300,null); } return new Result("注冊(cè)成功",200,null); } }
到此為止,驗(yàn)證碼的注冊(cè)功能就已經(jīng)實(shí)現(xiàn)完成了。
我們現(xiàn)在要做一個(gè)前端頁(yè)面。
四、創(chuàng)建登陸頁(yè)面
在resources目錄下創(chuàng)建static文件夾。在pages目錄下添加login.html和register.html。至于jquery呢就要大家自己去找了。
1 login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="../js/jquery.min.js"></script> <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" }else{ alert("登陸失敗,請(qǐng)檢查賬號(hào)或者密碼") } } }) }) }); </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"> <span class="top">郵箱簡(jiǎn)易發(fā)送系統(tǒng)</span><br> <span>賬號(hào):</span> <input type="text" placeholder="請(qǐng)輸入賬號(hào)" class="account"><br><br> <span>密碼:</span> <input type="password" placeholder="請(qǐng)輸入密碼" class="password"><br><br> <input type="button" value="登錄" class="btn"> <div class="a"> <a href="register.html" rel="external nofollow" >注冊(cè)</a> <a href="">忘記密碼</a> </div> </div> </body> </html>
2 register.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <link rel="stylesheet" href="../css/register.css" rel="external nofollow" > <script src="../js/jquery.min.js"></script> <script> $(function(){ // 注冊(cè)按鈕點(diǎn)擊事件 $(".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("該賬號(hào)已存在"); }else if(data.code === 300){ alert("賬號(hào)注冊(cè)失敗"); }else if(data.code === 400){ alert("驗(yàn)證碼錯(cuò)誤"); }else{ alert("賬號(hào)注冊(cè)成功"); window.location.href="../pages/login.html" rel="external nofollow" ; } } }); }); // 發(fā)送驗(yàn)證碼按鈕點(diǎn)擊事件 $(".sendCode").on("click", function(){ var email = $(".email").val().trim(); if(email === "") { alert("請(qǐng)輸入郵箱"); return; } // 發(fā)送驗(yàn)證碼請(qǐng)求 $.ajax({ type:"get", url:"/captcha/getCaptcha?email="+email, success:function(data){ console.log(data) if(data.code === 200){ alert("驗(yàn)證碼已發(fā)送到郵箱"); // 啟動(dòng)倒計(jì)時(shí) startCountdown(60); } else { alert("驗(yàn)證碼發(fā)送失敗,請(qǐng)重試"); } } }); }); // 啟動(dòng)倒計(jì)時(shí)函數(shù) function startCountdown(seconds) { var timer = seconds; $(".sendCode").prop("disabled", true); var countdown = setInterval(function() { $(".sendCode").val(timer + "秒后可重新發(fā)送"); timer--; if (timer < 0) { clearInterval(countdown); $(".sendCode").prop("disabled", false); $(".sendCode").val("發(fā)送驗(yàn)證碼"); } }, 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; /* 調(diào)整高度以適應(yīng)新字段 */ 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"> <span class="top">郵箱簡(jiǎn)易發(fā)送系統(tǒng)</span><br> <span>賬號(hào):</span> <input type="text" placeholder="請(qǐng)輸入賬號(hào)" class="account"><br><br> <span>密碼:</span> <input type="password" placeholder="請(qǐng)輸入密碼" class="password"><br><br> <span>郵箱:</span> <input type="text" placeholder="請(qǐng)輸入郵箱" class="email"><br><br> <input type="button" value="發(fā)送驗(yàn)證碼" class="sendCode"><br><br> <span>驗(yàn)證碼:</span> <input type="text" placeholder="請(qǐng)輸入驗(yàn)證碼" class="emailCode"><br><br> <input type="button" value="注冊(cè)" class="btn"> <div class="a"> <a href="login.html" rel="external nofollow" >返回</a> </div> </div> </body> </html>
到此這篇關(guān)于SpringBoot使用mail實(shí)現(xiàn)登錄郵箱驗(yàn)證的文章就介紹到這了,更多相關(guān)SpringBoot登錄郵箱驗(yàn)證內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- springboot項(xiàng)目接入第三方qq郵箱驗(yàn)證登錄的全過(guò)程
- Springboot實(shí)現(xiàn)郵箱驗(yàn)證碼注冊(cè)與修改密碼及登錄功能詳解流程
- SpringBoot登錄驗(yàn)證token攔截器的實(shí)現(xiàn)
- springboot整合shiro多驗(yàn)證登錄功能的實(shí)現(xiàn)(賬號(hào)密碼登錄和使用手機(jī)驗(yàn)證碼登錄)
- vue+springboot實(shí)現(xiàn)登錄驗(yàn)證碼
- springboot短信驗(yàn)證碼登錄功能的實(shí)現(xiàn)
- Springboot2.1.6集成activiti7出現(xiàn)登錄驗(yàn)證的實(shí)現(xiàn)
- springboot實(shí)現(xiàn)郵箱驗(yàn)證碼功能
- SpringBoot使用郵箱發(fā)送驗(yàn)證碼實(shí)現(xiàn)注冊(cè)功能
- SpringBoot發(fā)送郵箱驗(yàn)證碼功能
相關(guān)文章
jvm crash的崩潰日志詳細(xì)分析及注意點(diǎn)
本篇文章主要介紹了jvm crash的崩潰日志詳細(xì)分析及注意點(diǎn)。具有很好的參考價(jià)值,下面跟著小編一起來(lái)看下吧2017-04-04Spring MVC保證Controller并發(fā)安全的方法小結(jié)
在 Spring MVC 中,默認(rèn)情況下,@Controller 是單例的,這意味著所有請(qǐng)求共享一個(gè) Controller 實(shí)例,為確保并發(fā)安全,Spring 并不會(huì)自動(dòng)對(duì) Controller 進(jìn)行線程安全保護(hù),本文給大家介紹了Spring MVC保證Controller并發(fā)安全的方法,需要的朋友可以參考下2024-11-11springboot構(gòu)造樹形結(jié)構(gòu)數(shù)據(jù)并查詢的方法
本文主要介紹了springboot怎樣構(gòu)造樹形結(jié)構(gòu)數(shù)據(jù)并查詢,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11JavaScript中的isTrusted屬性及其應(yīng)用場(chǎng)景詳解
在現(xiàn)代 Web 開發(fā)中,JavaScript 是構(gòu)建交互式應(yīng)用的核心語(yǔ)言,隨著前端技術(shù)的不斷發(fā)展,開發(fā)者需要處理越來(lái)越多的復(fù)雜場(chǎng)景,例如事件處理、數(shù)據(jù)傳遞和狀態(tài)管理等,本文將通過(guò)一個(gè)實(shí)際案例,深入探討 isTrusted 屬性的來(lái)源、作用,需要的朋友可以參考下2025-01-01java使用ffmpeg實(shí)現(xiàn)上傳視頻的轉(zhuǎn)碼提取視頻的截圖等功能(代碼操作)
這篇文章主要介紹了java使用ffmpeg實(shí)現(xiàn)上傳視頻的轉(zhuǎn)碼,提取視頻的截圖等功能,本文圖文并茂給大家介紹的非常詳細(xì),對(duì)大家的工作或?qū)W習(xí)具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-03-03SpringCloud之LoadBalancer負(fù)載均衡服務(wù)調(diào)用過(guò)程
這篇文章主要介紹了SpringCloud之LoadBalancer負(fù)載均衡服務(wù)調(diào)用過(guò)程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-03-03