怎么实现一个登录时需要输入验证码的功能

最近给项目换了一个登录页面,而这个登录页面设计了验证码,于是想着把这个验证码功能实现一下吧。

这篇文章就如何实现登录时的验证码验证功能进行详细地介绍。

目录

页面效果

实现思路

生成验证码的控制器类

前端页面代码

后端登录代码

UserLoginDTO.java

UserController.java

 UserServiceImpl.java


页面效果

登录的时候会把用户名、密码和验证码一起传到后端,并对验证码进行验证,只有验证码正确才能登录。

实现思路

那么,具体是如何实现的呢,首先大概介绍一下我实现这个功能的思路:

  • 验证码图片的url由后端的一个Controller生成,前端请求这个接口的时候根据当前生成一个uuid,并把这个uuid在前端缓存起来,下一次还是从前端的缓存获取,在这里使用的是localStorage。
  • Controller生成验证码之后,把前端传过来的uuid通过redis缓存起来,这里分两次缓存
    • 缓存uuid
    • 以uuid为key,缓存验证码
  • 这样,当点击登录按钮将数据提交到后台登录接口时,会从redis中获取uuid,然后通过从redis中拿到的uuid去获取验证码,和前端用户输入的验证码进行比较。

由于博主也是第一次做这个功能,就随便巴拉巴拉找了一些生成验证码的工具,在这里是使用的kaptcha。

<!--生成验证码工具-->
<dependency><groupId>com.github.penggle</groupId><artifactId>kaptcha</artifactId><version>2.3.2</version>
</dependency><dependency><groupId>com.github.whvcse</groupId><artifactId>easy-captcha</artifactId><version>1.6.2</version>
</dependency>

生成验证码的控制器类

package cn.edu.sgu.www.mhxysy.controller;import cn.edu.sgu.www.mhxysy.annotation.AnonymityAccess;
import cn.edu.sgu.www.mhxysy.config.CaptchaConfig;
import cn.edu.sgu.www.mhxysy.exception.GlobalException;
import cn.edu.sgu.www.mhxysy.restful.ResponseCode;
import cn.edu.sgu.www.mhxysy.util.UserUtils;
import com.wf.captcha.GifCaptcha;
import com.wf.captcha.SpecCaptcha;
import com.wf.captcha.base.Captcha;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/*** @author heyunlin* @version 1.0*/
@Slf4j
@RestController
@Api(tags = "验证码管理")
@RequestMapping(value = "/captcha", produces = "application/json;charset=utf-8")
public class CaptchaController {private final CaptchaConfig captchaConfig;private final StringRedisTemplate stringRedisTemplate;@Autowiredpublic CaptchaController(CaptchaConfig captchaConfig, StringRedisTemplate stringRedisTemplate) {this.captchaConfig = captchaConfig;this.stringRedisTemplate = stringRedisTemplate;}/*** 生成验证码* @param type 验证码图片类型* @param uuid 前端生成的uuid*/@AnonymityAccess@ApiOperation("生成验证码")@RequestMapping(value = "/generate", method = RequestMethod.GET)public void generate(@RequestParam String type, @RequestParam String uuid) throws IOException {// 获取HttpServletResponse对象HttpServletResponse response = UserUtils.getResponse();// 设置请求头response.setContentType("image/gif");response.setDateHeader("Expires", 0);response.setHeader("Pragma", "No-cache");response.setHeader("Cache-Control", "no-cache");Captcha captcha;Integer width = captchaConfig.getWidth();Integer height = captchaConfig.getHeight();switch (type) {case "png":captcha = new SpecCaptcha(width, height);break;case "gif":captcha = new GifCaptcha(width, height);break;default:throw new GlobalException(ResponseCode.BAD_REQUEST, "不合法的验证码类型:" + type);}captcha.setLen(4);captcha.setCharType(Captcha.TYPE_DEFAULT);String code = captcha.text();log.debug("生成的验证码:{}", code);// 缓存验证码stringRedisTemplate.opsForValue().set("uuid", uuid);stringRedisTemplate.opsForValue().set(uuid, code);// 输出图片流captcha.out(response.getOutputStream());}}

 

前端页面代码

localStorage.js

/*** 保存数据到localStorage* @param name 数据的名称* @param value 数据的值*/
function storage(name, value) {localStorage.setItem(name, value);
}/*** localStorage根据name获取value* @param name 数据的名称*/
function getStorage(name) {return localStorage.getItem(name);
}

 

login.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>梦幻西游手游管理登录</title><link rel="stylesheet" href="/css/login.css"><script src="/js/public/jquery.min.js"></script><script src="/js/public/util.js"></script><script src="/js/public/localStorage.js"></script><script src="/js/login.js"></script></head><body style="overflow:hidden"><div class="pagewrap"><div class="main"><div class="header"></div><div class="content"><div class="con_left"></div><div class="con_right"><div class="con_r_top"><a href="javascript:" class="left">下载游戏</a><a href="javascript:" class="right">登录管理</a></div><ul><li class="con_r_left" style="display:none;"><div class="erweima"><div class="qrcode"><div id="output"><img src="/images/login/mhxysy.png" /></div></div></div><div style="height:70px;"><p>扫码下载梦幻西游手游</p></div></li><li class="con_r_right" style="display:block;"><div><div class="user"><div><span class="user-icon"></span><input type="text" id="login_username" /></div><div><span class="mima-icon"></span><input type="password" id="login_password" /></div><div><span class="yzmz-icon"></span><input type="text" id="code" />  <img id="captcha" alt="看不清?点击更换" /></div></div><br><button id="btn_Login" type="button">登 录</button></div></li></ul></div></div></div></div></body>
</html>

login.js

/*** 禁止输入空格*/
function preventSpace() {let event = window.event;if(event.keyCode === 32) {event.returnValue = false;}
}// 登录
function login() {let username = $("#login_username").val();let password = $("#login_password").val();let code = $("#code").val();if (!username) {alert("请输入用户名!");$("#login_username").focus();} else if (!password) {alert("请输入密码!");$("#login_password").focus();} else if (!code) {alert("请输入验证码!");} else {post("/user/login", {username: username,password: password,code: code}, function() {location.href = "/index.html";}, function (res) {if (res && res.responseJSON) {let response = res.responseJSON;if (res.status && res.status === 404) {let message;if(response.path) {message = "路径" + response.path + "不存在。";} else {message = response.message;}alert(message);} else {alert(response.message);}}});}
}$(function() {$("#login_username").keydown(function() {preventSpace();}).attr("placeholder", "请输入用户名");/*** 给密码输入框绑定回车登录事件*/$("#login_password").keydown(function(event) {if(event.keyCode === 13) {login();}preventSpace();}).attr("placeholder", "请输入密码");$("#code").keydown(function() {preventSpace();}).attr("placeholder", "验证码");// 获取uuidlet uuid = getStorage("uuid");if (!uuid) {uuid = new Date().toDateString();storage("uuid", uuid);}$("#captcha").attr("src", "/captcha/generate?type=png&uuid=" + uuid);// 点击登录按钮$("#btn_Login").on("click", function () {login();});$(".content .con_right .left").on("click", function () {$(this).css({"color": "#333333","border-bottom": "2px solid #2e558e"});$(".content .con_right .right").css({"color": "#999999","border-bottom": "2px solid #dedede"});$(".content .con_right ul .con_r_left").css("display", "block");$(".content .con_right ul .con_r_right").css("display", "none");});$(".content .con_right .right").on("click", function () {$(this).css({"color": "#333333","border-bottom": "2px solid #2e558e"});$(".content .con_right .left").css({"color": "#999999","border-bottom": "2px solid #dedede"});$(".content .con_right ul .con_r_right").css("display", "block");$(".content .con_right ul .con_r_left").css("display", "none");});});

 

后端登录代码

UserLoginDTO.java

package cn.edu.sgu.www.mhxysy.dto.system;import lombok.Data;import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.io.Serializable;/*** @author heyunlin* @version 1.0*/
@Data
public class UserLoginDTO implements Serializable {private static final long serialVersionUID = 18L;/*** 验证码*/@NotNull(message = "验证码不允许为空")@NotEmpty(message = "验证码不允许为空")private String code;/*** 用户名*/@NotNull(message = "用户名不允许为空")@NotEmpty(message = "用户名不允许为空")private String username;/*** 密码*/@NotNull(message = "密码不允许为空")@NotEmpty(message = "密码不允许为空")private String password;
}

 

UserController.java

package cn.edu.sgu.www.mhxysy.controller.system;import cn.edu.sgu.www.mhxysy.annotation.AnonymityAccess;
import cn.edu.sgu.www.mhxysy.annotation.Exclusion;
import cn.edu.sgu.www.mhxysy.dto.system.UserLoginDTO;
import cn.edu.sgu.www.mhxysy.restful.JsonResult;
import cn.edu.sgu.www.mhxysy.service.system.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;/*** @author heyunlin* @version 1.0*/
@Exclusion
@RestController
@Api(tags = "用户管理")
@RequestMapping(path = "/user", produces="application/json;charset=utf-8")
public class UserController {private final UserService userService;@Autowiredpublic UserController(UserService userService) {this.userService = userService;}@AnonymityAccess@ApiOperation("登录认证")@RequestMapping(value = "/login", method = RequestMethod.POST)public JsonResult<Void> login(@Validated UserLoginDTO loginDTO) {userService.login(loginDTO);return JsonResult.success();}/*省略的其他代码*/}

 

 UserServiceImpl.java

package cn.edu.sgu.www.mhxysy.service.system.impl;import cn.edu.sgu.www.mhxysy.dto.system.UserLoginDTO;
import cn.edu.sgu.www.mhxysy.entity.system.User;
import cn.edu.sgu.www.mhxysy.entity.system.UserLoginLog;
import cn.edu.sgu.www.mhxysy.exception.GlobalException;
import cn.edu.sgu.www.mhxysy.feign.FeignService;
import cn.edu.sgu.www.mhxysy.redis.RedisRepository;
import cn.edu.sgu.www.mhxysy.restful.ResponseCode;
import cn.edu.sgu.www.mhxysy.service.system.UserService;
import cn.edu.sgu.www.mhxysy.util.IpUtils;
import cn.edu.sgu.www.mhxysy.util.StringUtils;
import cn.edu.sgu.www.mhxysy.util.UserUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;import java.time.LocalDateTime;/*** @author heyunlin* @version 1.0*/
@Slf4j
@Service
public class UserServiceImpl implements UserService {private final FeignService feignService;private final RedisRepository redisRepository;private final StringRedisTemplate stringRedisTemplate;@Value("${syslog.enable}")private boolean enable;@Autowiredpublic UserServiceImpl(FeignService feignService,RedisRepository redisRepository,StringRedisTemplate stringRedisTemplate) {this.feignService = feignService;this.redisRepository = redisRepository;this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic void login(UserLoginDTO loginDTO) {String code = loginDTO.getCode();String uuid = stringRedisTemplate.opsForValue().get("uuid");if (uuid == null) {throw new GlobalException(ResponseCode.BAD_REQUEST, "获取验证码失败~");}if (!code.equalsIgnoreCase(stringRedisTemplate.opsForValue().get(uuid))) {throw new GlobalException(ResponseCode.BAD_REQUEST, "验证码错误~");}// 得到用户名String username = loginDTO.getUsername();log.debug("用户{}正在登录...", username);// 查询用户信息,如果用户被锁定,提前退出User user = feignService.selectByUsername(username);if (user != null) {if (user.getEnable()) {// shiro登录认证UsernamePasswordToken token = new UsernamePasswordToken(username, loginDTO.getPassword());Subject subject = UserUtils.getSubject();subject.login(token);// 设置session失效时间:永不超时subject.getSession().setTimeout(-1001);// 修改管理员上一次登录时间User usr = new User();usr.setId(user.getId());usr.setLastLoginTime(LocalDateTime.now());feignService.updateById(usr);// 如果开启了系统日志if (enable) {// 添加管理员登录历史UserLoginLog loginLog = new UserLoginLog();loginLog.setId(StringUtils.uuid());loginLog.setUserId(user.getId());loginLog.setLoginTime(LocalDateTime.now());loginLog.setLoginIp(IpUtils.getLocalHostAddress());loginLog.setLoginHostName(IpUtils.getLocalHostName());feignService.saveLoginLog(loginLog);}// 从redis中删除用户权限redisRepository.remove(username);// 查询用户的权限信息,并保存到redisredisRepository.save(username);} else {throw new GlobalException(ResponseCode.FORBIDDEN, "账号已被锁定,禁止登录!");}} else {throw new GlobalException(ResponseCode.NOT_FOUND, "用户名不存在~");}}}

好了,文章就分享到这里了,看完要是觉得对你有所帮助,不要忘了点赞+收藏哦~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/110317.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JL-A/41 JL-A/42 JL-A/43 集成电路电流继电器 过负荷或短路 JOSEF约瑟

JL-A、B集成电路电流继电器 JL-A/11 JL-A/31 JL-A/12 JL-A/32 JL-A/13 JL-A/33 JL-A/21 JL-A/22 JL-A/23 JL-A/34 JL-A/35 JL-B/41 JL-A/42 JL-B/43 JL-B/11 JL-B/31 JL-B/12 JL-B/32 JL-B/13 JL-B/33 JL-B/21 JL-B/22 JL-B/23 JL-B/34 JL-B/35 JL-B/41 JL-B/42 …

[NLP] LLM---<训练中文LLama2(一)>训练一个中文LLama2的步骤

一 数据集 【Awesome-Chinese-LLM中文数据集】 【awesome-instruction-dataset】【awesome-instruction-datasets】【LLaMA-Efficient-Tuning-数据集】Wiki中文百科&#xff08;25w词条&#xff09;wikipedia-cn-20230720-filteredBaiduBaiKe&#xff08;563w词条&#xff09; …

linux内核分析:进程与调度

lec12, 13, 14 : 进程数据结构 实际上进程和线程都是有一个统一的内部结构------task_struct 这是因为,上面的进程和线程到了内核这里,统一变成了任务,这就带来两个问题:信息展示和命令下达(有的是进程,有的是线程下达) pid 是 process id,tgid 是 thread group ID …

Python模块之time中时间戳、时间字符与时间元组之间的相互转换

时间的三种形式 时间戳&#xff0c;根据1970年1月1日00:00:00开始按秒计算的偏移量。 1694868399 时间格式字符串&#xff0c;字符串形式的时间。 2023-09-16 20:46:39 时间元组&#xff08;struct_time&#xff09;&#xff0c;包含9个元素。 time.struct_time(tm_year2023, …

026-从零搭建微服务-文件服务(二)

写在最前 如果这个项目让你有所收获&#xff0c;记得 Star 关注哦&#xff0c;这对我是非常不错的鼓励与支持。 源码地址&#xff08;后端&#xff09;&#xff1a;https://gitee.com/csps/mingyue 源码地址&#xff08;前端&#xff09;&#xff1a;https://gitee.com/csps…

【C++】使用红黑树进行封装map和set

&#x1f307;个人主页&#xff1a;平凡的小苏 &#x1f4da;学习格言&#xff1a;命运给你一个低的起点&#xff0c;是想看你精彩的翻盘&#xff0c;而不是让你自甘堕落&#xff0c;脚下的路虽然难走&#xff0c;但我还能走&#xff0c;比起向阳而生&#xff0c;我更想尝试逆风…

Python Opencv实践 - 视频文件操作

参考资料&#xff1a; 视频处理VideoCapture类---OpenCV-Python开发指南&#xff08;38&#xff09;_python opencv videocapture_李元静的博客-CSDN博客 OpenCV VideoCapture.get()参数详解 - 简书FOURCC四字符码对照表_4fvcc_Kellybook的博客-CSDN博客 import cv2 as cv im…

02目标检测-传统检测方法

目录 一、目标学习的检测方法变迁及对比 二、 基于传统手工特征的检测算法的定义 三、传统主要手工特征与算法 Haar特征与 人脸检测算法 - Viola-Jones(了解) HOG特征与 SVM 算法(了解)&#xff08;行人检测、opencv实现&#xff09; SIFT特征与SIFT算法(了解) DPM&#…

Python中异常处理4-4

在Python中的异常处理4-1_棉猴的博客-CSDN博客中提到&#xff0c;在try块中的代码运行时如果出现异常&#xff0c;会自动抛出这个异常。可以通过raise语句手动抛出异常。 1 raise语句手动抛出异常 raise后面跟要抛出的异常类或者异常类的实例&#xff0c;表示手动抛出该异常&…

看好多人都在劝退学计算机,可是张雪峰又 推荐过计算机,所以计算机到底是什么样 的?

张雪峰高考四百多分&#xff0c;但是他现在就瞧不起400多分的学生。说难听点&#xff0c;六七百分的 热门专业随便报谁不会啊&#xff1f; 计算机专业全世界都是过剩的&#xff0c;今年桂林电子科技&#xff0c;以前还是华为的校招大学&#xff0c;今年 计算机2/3待业。这个世…

听GPT 讲Istio源代码--istioctl

在 Istio 项目的 istioctl 目录中&#xff0c;有一些子目录&#xff0c;每个目录都有不同的作用和功能。以下是这些子目录的详细介绍&#xff1a; /pkg: pkg 目录包含了 istioctl 工具的核心代码和库。这些代码和库提供了与 Istio 控制平面交互的功能&#xff0c;例如获取和修改…

java:逆序排序的三种方法

// 逆序第一种方法 public static void main(String[] args) {int arr[] {11, 22, 33, 44, 55, 66};for (int i arr.length-1; i > 0; i--) {System.out.print("\t"arr[i]);}}缺点&#xff1a;这个是直接逆转&#xff0c;如果里面是随机数没办法比较 逆序第二种…