网站框架包括哪些/国际新闻头条今日国际大事
今天给项目换了一个登录页面,而这个登录页面设计了验证码,于是想着把这个验证码功能实现一下吧。
这篇文章就如何实现登录时的验证码的验证功能结合代码进行详细地介绍,以及介绍功能实现的思路。
目录
页面效果
实现思路
生成验证码的控制器类
前端页面代码
localStorage.js
login.html
login.js
后端登录代码
UserLoginDTO.java
UserController.java
UserServiceImpl.java
潜在问题
改进方案
页面效果
登录的时候会把用户名、密码和验证码一起传到后端,并对验证码进行验证,只有验证码正确才能登录。
实现思路
那么,具体是如何实现的呢,首先大概介绍一下我实现这个功能的思路:
- 验证码图片的url由后端的一个Controller生成,前端请求这个接口的时候根据当前生成一个uuid,并把这个uuid在前端缓存起来,下一次还是从前端的缓存获取,在这里使用的是localStorage。
- Controller生成验证码之后,把前端传过来的uuid通过redis缓存起来,这里分两次缓存
- 缓存uuid
- 以uuid为key,缓存验证码
- 这样,当点击登录按钮将数据提交到后台登录接口时,会从redis中获取uuid,然后通过从redis中拿到的uuid去获取验证码,和前端用户输入的验证码进行比较。
由于博主也是第一次做这个功能,就随便在网上找了一个生成验证码的工具easy-captcha
<!--生成验证码工具-->
<dependency><groupId>com.github.whvcse</groupId><artifactId>easy-captcha</artifactId><version>1.6.2</version>
</dependency>
生成验证码的控制器类
CaptchaController.java
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);// 保存uuidstringRedisTemplate.opsForValue().set("uuid", uuid);stringRedisTemplate.opsForValue().expire("uuid", 5, TimeUnit.MINUTES);// 缓存验证码stringRedisTemplate.opsForValue().set(uuid, code);stringRedisTemplate.opsForValue().expire(uuid, 5, TimeUnit.MINUTES);// 输出图片流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().getTime();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");// 得到的uuid为空,则获取验证码到登录之间的时间已经过了5分钟,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, "用户名不存在~");}}}
潜在问题
这样的设计会有一个问题,uuid这个key有可能会被其他用户修改,但是验证码并不会被修改。
改进方案
目前正在找解决方案~
好了,文章就分享到这里了,看完要是觉得对你有所帮助,不要忘了点赞+收藏哦~
相关文章:

怎么实现一个登录时需要输入验证码的功能
今天给项目换了一个登录页面,而这个登录页面设计了验证码,于是想着把这个验证码功能实现一下吧。 这篇文章就如何实现登录时的验证码的验证功能结合代码进行详细地介绍,以及介绍功能实现的思路。 目录 页面效果 实现思路 生成验证码的控制…...

在android工程中新建Android模块报错
复制了复制正常的build.gradle文件,然后把theme里面的东西改成了下面这个样就好了 <resources xmlns:tools"http://schemas.android.com/tools"><!-- Base application theme. --><style name"Theme.JiQuan" parent"Theme…...

电脑桌面的复选框如何取消
电脑桌面图标的复选框如何取消 1. 概述2. 去掉图标的复选框方法结束语 1. 概述 当你拿到新的电脑开机后,发现桌面上软件应用的图标左上角有个小框,每次点击图标都会显示,并且点击图标时,小框还会打上√; 这个小框的…...

【Unity每日一记】资源加载相关和检测相关
👨💻个人主页:元宇宙-秩沅 👨💻 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 👨💻 本文由 秩沅 原创 👨💻 收录于专栏:uni…...

【数据结构】长篇详解堆,堆的向上/向下调整算法,堆排序及TopK问题
文章目录 堆的概念性质图解 向上调整算法算法分析代码整体实现 向下调整算法算法分析整体代码实现 堆的接口实现初始化堆销毁堆插入元素删除元素打印元素判断是否为空取首元素实现堆 堆排序创建堆调整堆整合步骤 TopK问题 堆的概念 堆就是将一组数据所有元素按完全二叉树的顺序…...

DAQ高频量化平台:引领Ai高频量化交易模式变革
近年来,数字货币投资市场掀起了一股热潮,以(BTC)为代表的区块链技术带来了巨大的商业变革。数字资产的特点,如无国界、无阶级、无门槛、高流动性和高透明度,吸引了越来越多的人们的关注和认可,创…...

vue3 element plus获取el-cascader级联选择器选中的当前结点的label值 附vue2获取当前label
各位大佬,有时我们在处理级联选择组件数据时,不仅需要拿到id,还需要拿到label名称,但是通常组件直接绑定的是id,所以就需要我们用别的方法去拿到label,此处官方是有这个方法的,具体根据不同的element 版本进行分别处理。 VUE3 e…...

Spring Boot常见面试题
Spring Boot简介 Spring Boot 是由 Pivotal 团队提供,用来简化 Spring 应用创建、开发、部署的框架。它提供了丰富的Spring模块化支持,可以帮助开发者更轻松快捷地构建出企业级应用。Spring Boot通过自动配置功能,降低了复杂性,同…...

分块矩阵求逆
另可参考Block matrix on Wikipedia2018.4.3 补充补充两个参考文献,都是对工科很实用的矩阵手册:D. S. Bernstein, Matrix mathematics: Theory, facts, and formulas with application to linear systems theory. Princeton, NJ: Princeton University …...

Python 文件写入操作
视频版教程 Python3零基础7天入门实战视频教程 w模式是写入,通过write方法写入内容。 # 打开文件 模式w写入,文件不存在,则自动创建 f open("D:/测试3.txt", "w", encoding"UTF-8")# write写入操作 内容写入…...

【Spring Boot系列】- Spring Boot侦听器Listener
【Spring Boot系列】- Spring Boot侦听器Listener 文章目录 【Spring Boot系列】- Spring Boot侦听器Listener一、概述二、监听器Listener分类2.1 监听ServletContext的事件监听器2.2 监听HttpSeesion的事件监听器2.3 监听ServletRequest的事件监听器 三、SpringMVC中的监听器3…...

JavaScript速成课—事件处理
目录 一.事件类型 1.窗口事件 2.表单元素事件 3.图像事件 4.键盘事件 5.鼠标事件 二.JavaScript事件处理的基本机制 三.绑定事件的方法 1.DOM元素绑定 2.JavaScript代码绑定事件 3.监听事件函数绑定 四.JavaScript事件的event对象 1.获取event对象 2.鼠标坐标获取…...

【入门篇】ClickHouse最优秀的开源列式存储数据库
文章目录 一、什么是ClickHouse?OLAP场景的关键特征列式数据库更适合OLAP场景的原因输入/输出CPU 1.1 ClickHouse的定义与发展历程1.2 ClickHouse的版本介绍 二、ClickHouse的主要特性2.1 高性能的列式存储2.2 实时的分析查询2.3 高度可扩展性2.4 数据压缩2.5 SQL支…...

【C++ Exceptions】异常处理的成本
最低成本 exception是C的一部分,编译器必须支持。即使从未使用任何异常处理机制,也必须付出一些空间放置某些数据结构,付出一些时间随时保持那些数据结构的正确性。 第二种成本:来自try语句块 避免非必要的try语句块。 粗略估计&a…...

API接口:原理、实现及应用
API(Application Programming Interface)接口是现代软件开发中不可或缺的一部分。它们提供了一种机制,使得不同的应用程序和服务可以相互通信,共享数据和功能。在这篇文章中,我们将探讨API接口的原理、实现及应用&…...

SpringBoot学习笔记(项目创建,yaml,多环境开发,整合mybatis SMM)
一、SpringBoot入门 1.1 SpringBoot概述 SpringBoot是由Pivotal团队提供的全新框架,其设计目的是用来简化Spring应用的初始搭建以及开发过程。 Spring程序缺点:配置繁琐,依赖设置繁琐。SpringBoot程序优点:自动装配,…...

Linux内核分析:输入输出,字符与块设备 31-35
CPU 并不直接和设备打交道,它们中间有一个叫作设备控制器(Device Control Unit)的组件,例如硬盘有磁盘控制器、USB 有 USB 控制器、显示器有视频控制器等。这些控制器就像代理商一样,它们知道如何应对硬盘、鼠标、键盘、显示器的行为。 输入输出设备我们大致可以分为两类…...

Linux抓包工具tcpdump
一、介绍 tcpdump是一个抓包工具,用于实时捕获和分析网络流量。它通常在unix和linux操作系统上使用。tcpdump能够捕获流经网络接口的数据包,并显示或保存它们以供进一步分析。它提供有关每个数据包的详细信息,包括源IP地址、目标IP地址、使用…...

Qt消息机制和事件
事件 事件是由Qt或者系统在不同时刻发出的,当敲下鼠标,或者按下键盘,或者当窗口需要重新绘制的时候,就会发出一个相应的事件,一些操作由用户的操作发出,一些则由系统自动发出,如系统定时器事件等。 Qt 中所有事件类都继承于 QEvent。 在事件对象创建完毕后, Qt 将这个…...

LeetCode-739-每日温度-单调栈
题目描述:给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。 题目…...

MyBatis中当实体类中的属性名和表中的字段名不一样,怎么办
方法1: 在mybatis核心配置文件中指定,springboot加载mybatis核心配置文件 springboot项目的一个特点就是0配置,本来就省掉了mybatis的核心配置文件,现在又加回去算什么事,总之这种方式可行但没人这样用 具体操作&…...

Flutter框架和原理剖析
Flutter是Google推出并开源的跨平台开发框架,主打跨平台、高保真、高性能。开发者可以通过Dart语言开发Flutter应用,一套代码同时运行在ios和Android平台。不仅如此,flutter还支持web、桌面、嵌入应用的开发。flutter提供了丰富的组件、接口&…...

NFS:使用 Ansible 自动化配置 NFS 客户端服务端
考试顺便整理博文内容整理涉及使用 Ansible 部署 NFS 客户端和服务端理解不足小伙伴帮忙指正 对每个人而言,真正的职责只有一个:找到自我。然后在心中坚守其一生,全心全意,永不停息。所有其它的路都是不完整的,是人的逃…...

IntelliJ IDEA使用——Debug操作
文章目录 版本说明图标和快捷键查看变量计算表达式条件断点多线程调试 版本说明 当前的IntelliJ IDEA 的版本是2021.2.2(下载IntelliJ IDEA) ps:不同版本一些图标和设置位置可能会存在差异,但应该大部分都差不多。 图标和快捷键…...

uniapp项目实践总结(十八)自定义多列瀑布流组件
导语:有时候展示图片等内容,会遇到图片高度不一致的情况,这时候就不能使用等高双列或多列展示了,这时候会用到瀑布流的页面布局,下面就一起探讨一下瀑布流的实现方法。 目录 准备工作原理分析实战演练案例展示 准备工…...

Ubuntu 22.04LTS + 深度学习环境安装全流程
一、 CUDA Toolkit 安装 1. 选择需要安装的版本(下载地址) 2. 选择自己的系统版本获取下载地址和安装指令 3. 运行安装指令进行安装 wget https://developer.download.nvidia.com/compute/cuda/12.2.2/local_installers/cuda_12.2.2_535.104.05_linux.run sudo sh cuda_12.2.…...

【lesson7】git的介绍及使用
文章目录 什么是gitgit的历史git使用在gitee上创建仓库git clone HTTPS地址git add .git add 文件名git commit “日志”git pushgit loggit rm 文件名git statusgit pull 什么是git git是版本控制器,那么什么是版本控制器呢? 下面讲个故事为大家讲解一…...

Keepalived+LVS高可用集群
目录 一、keepalived介绍: 二、keepalived工具介绍: (1)管理 LVS 负载均衡软件: (2)支持故障自动切换: (3)实现 LVS 负载调度器、节点服务器的高可用性&…...

AK 9.12 百度Java后端研发B卷 笔试
T1(博弈论) #include <bits/stdc.h>#define endl \nusing namespace std;typedef long long LL;const int N 1e5 10;int n, m, t;void solve() {cin >> n >> m; t n m - 2;if(t & 1) cout << "Yes" << endl;else cout <&l…...

使用Python和XPath解析动态JSON数据
JSON动态数据在Python中扮演着重要的角色,为开发者提供了处理实时和灵活数据的能力。Python作为一种强大的编程语言,提供了丰富的工具和库来处理动态JSON数据使得解析和处理动态JSON数据变得简单和高效。例如,使用内置的json模块,…...