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

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

这篇文章就如何实现登录时的验证码的验证功能结合代码进行详细地介绍,以及介绍功能实现的思路。

目录

页面效果

实现思路

前端页面代码

localStorage.js

login.html

login.js

后端Java代码

UserLoginDTO.java

UserController.java

UserServiceImpl.java

CaptchaController.java

潜在问题

改进方案

修改login.js

修改UserLoginDTO

修改CaptchaController

修改UserServiceImpl.login()


页面效果

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

实现思路

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

  • 验证码图片的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>

前端页面代码

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");});});

后端Java代码

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, "用户名不存在~");}}}

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());}}

潜在问题

这样的设计可能会导致以下问题:

  • 多个用户同一时间访问登录页面,导致生成的uuid一样,数据会互相覆盖;
  • uuid这个key被其他用户修改;

改进方案

前端生成随机的uuid,并缓存到localStorage,登陆的时候也把这个uuid传到后端,这样就解决了key重复和key被覆盖的问题。

修改login.js

由之前的通过当前时间的时间戳生成uuid的方式改为通过使用Crypto API生成随机字符串。并且,登录的时候把这个uuid也一起携带到登录接口。

/*** 禁止输入空格*/
function preventSpace() {let event = window.event;if(event.keyCode === 32) {event.returnValue = false;}
}/*** 生成随机字符串* @param length 字符串的长度,默认11* @returns {string}*/
function generateRandomString(length = 11) {let charset = "abcdefghijklmnopqrstuvwxyz-_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";let values = new Uint32Array(length);window.crypto.getRandomValues(values);let str = "";for (let i = 0; i < length; i++) {str += charset[values[i] % charset.length];}return str;
}/*** 登录*/
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,uuid: getStorage("uuid")}, 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);}} else {alert("请求无响应~");}});}
}$(function() {$("#login_username").keydown(function() {preventSpace();}).attr("placeholder", "请输入用户名");$("#login_password").keydown(function() {preventSpace();}).attr("placeholder", "请输入密码");/*** 给验证码输入框绑定回车登录事件*/$("#code").keydown(function(event) {if(event.keyCode === 32) {event.returnValue = false;} else if(event.keyCode === 13) {login();}}).attr("placeholder", "验证码");// 获取uuidlet uuid = getStorage("uuid");if (!uuid) {//uuid = new Date().getTime();uuid = generateRandomString();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

添加字段uuid,并且要求必填

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;/*** uuid:随机字符串*/@NotNull(message = "验证码已过期,请刷新页面重新获取")@NotEmpty(message = "验证码已过期,请刷新页面重新获取")private String uuid;/*** 验证码*/@NotNull(message = "验证码不允许为空")@NotEmpty(message = "验证码不允许为空")private String code;/*** 用户名*/@NotNull(message = "用户名不允许为空")@NotEmpty(message = "用户名不允许为空")private String username;/*** 密码*/@NotNull(message = "密码不允许为空")@NotEmpty(message = "密码不允许为空")private String password;
}

修改CaptchaController

删除缓存uuid的代码

package cn.edu.sgu.www.mhxysy.controller;import cn.edu.sgu.www.mhxysy.annotation.AnonymityAccess;
import cn.edu.sgu.www.mhxysy.config.CaptchaProperties;
import cn.edu.sgu.www.mhxysy.consts.RedisKeyPrefixConst;
import cn.edu.sgu.www.mhxysy.exception.GlobalException;
import cn.edu.sgu.www.mhxysy.redis.StringRedisUtils;
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.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;
import java.util.concurrent.TimeUnit;/*** @author heyunlin* @version 1.0*/
@Slf4j
@RestController
@Api(tags = "验证码管理")
@RequestMapping(value = "/captcha", produces = "application/json;charset=utf-8")
public class CaptchaController {private final CaptchaProperties captchaProperties;private final StringRedisUtils stringRedisUtils;@Autowiredpublic CaptchaController(CaptchaProperties captchaProperties, StringRedisUtils stringRedisUtils) {this.captchaProperties = captchaProperties;this.stringRedisUtils = stringRedisUtils;}/*** 生成验证码* @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 = captchaProperties.getWidth();Integer height = captchaProperties.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);// 缓存验证码String key = RedisKeyPrefixConst.PREFIX_CAPTCHA + uuid;stringRedisUtils.set(key , code);// 设置验证码3分钟后过期stringRedisUtils.expire(key, 3, TimeUnit.MINUTES);// 输出图片流captcha.out(response.getOutputStream());}}

修改UserServiceImpl.login()

修改uuid的获取方式,直接从登录的dto对象中获取,而不是从缓存中获取。

package cn.edu.sgu.www.mhxysy.service.system.impl;import cn.edu.sgu.www.mhxysy.config.EmailProperties;
import cn.edu.sgu.www.mhxysy.consts.RedisKeyPrefixConst;
import cn.edu.sgu.www.mhxysy.dto.system.UserLoginDTO;
import cn.edu.sgu.www.mhxysy.dto.system.UserUpdateDTO;
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.redis.StringRedisUtils;
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.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;/*** @author heyunlin* @version 1.0*/
@Slf4j
@Service
public class UserServiceImpl implements UserService {@Value("${syslog.enable}")private boolean enable;private final FeignService feignService;private final JavaMailSender javaMailSender;private final EmailProperties emailProperties;private final RedisRepository redisRepository;private final StringRedisUtils stringRedisUtils;@Autowiredpublic UserServiceImpl(FeignService feignService,JavaMailSender javaMailSender,EmailProperties emailProperties,RedisRepository redisRepository,StringRedisUtils stringRedisUtils) {this.feignService = feignService;this.javaMailSender = javaMailSender;this.emailProperties = emailProperties;this.redisRepository = redisRepository;this.stringRedisUtils = stringRedisUtils;}@Overridepublic void logout() {// 删除角色的权限redisRepository.delete(UserUtils.getLoginUsername());// 注销UserUtils.getSubject().logout();}@Overridepublic void login(UserLoginDTO loginDTO) {// 一、验证码判断// 得到用户输入的验证码String code = loginDTO.getCode();// 获取正确的验证码String uuid = loginDTO.getUuid();String key = RedisKeyPrefixConst.PREFIX_CAPTCHA + uuid;String realCode = stringRedisUtils.get(key);// 得到的验证码为空,则获取验证码到登录之间的时间已经过了3分钟,验证码过期已经被删除if (realCode == null) {throw new GlobalException(ResponseCode.BAD_REQUEST, "验证码已失效,请刷新页面重新获取~");}// 验证码校验if (!code.equalsIgnoreCase(realCode)) {throw new GlobalException(ResponseCode.BAD_REQUEST, "验证码错误~");}// 二、登录流程// 得到用户名String username = loginDTO.getUsername();log.debug("用户{}正在登录...", username);// 查询用户信息,如果用户被锁定,提前退出User user = feignService.selectByUsername(username);if (user != null) {if (user.getEnable()) {// 1、shiro登录认证UsernamePasswordToken token = new UsernamePasswordToken(username, loginDTO.getPassword());Subject subject = UserUtils.getSubject();subject.login(token);// 设置session失效时间:永不超时subject.getSession().setTimeout(-1001);// 2、修改管理员上一次登录时间User usr = new User();usr.setId(user.getId());usr.setLastLoginTime(LocalDateTime.now());feignService.updateById(usr);// 3、邮件通知Thread thread = new Thread() {@Overridepublic void run() {// 定义日期格式DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");MimeMessage message = javaMailSender.createMimeMessage();MimeMessageHelper helper = new MimeMessageHelper(message);try {String text = "您的账号" + username + "在广州登录了。" +"[" + LocalDateTime.now().format(formatter) + "]";helper.setFrom(emailProperties.getFrom());helper.setTo(emailProperties.getTo());helper.setText(text);javaMailSender.send(message);} catch (MessagingException e) {e.printStackTrace();}}};thread.start();// 4、如果开启了系统日志,添加管理员登录历史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);}// 5、从redis中删除用户权限redisRepository.delete(username);// 6、查询用户的权限信息,并保存到redisredisRepository.save(username);} else {throw new GlobalException(ResponseCode.FORBIDDEN, "账号已被锁定,禁止登录!");}} else {throw new GlobalException(ResponseCode.NOT_FOUND, "用户名不存在~");}}@Overridepublic void updatePass(UserUpdateDTO userUpdateDTO) {feignService.updatePass(userUpdateDTO);}}

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

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

conda常用指令

常用conda指令 查看当前有哪些环境&#xff0c;有base环境 conda env list 创建环境 # conda create -n 你的环境名 python版本号 # 创建python3.10&#xff0c;名为env虚拟环境 conda create -n env python3.10 激活环境 conda activate env

SpringCloud Alibaba - Sentinel篇

一、Sentinel快速入门 Sentinel官网地址&#xff1a;https://sentinelguard.io/zh-cn/index.html Sentinel项目地址&#xff1a;https://github.com/alibaba/Sentinel Sentinel是阿里巴巴开源的一款微服务流量治理组件&#xff0c;主要以流量为切入点&#xff0c;从流量限流、熔…

Linux MyFile

在之前&#xff0c;我们应该都多少接触过了C语言的文件管理&#xff0c;fopen&#xff0c;fclose&#xff0c;fputs....等函数的用法&#xff0c;也分析了系统层面上C语言是如何实现文件管理的。 回顾 上一个文章&#xff0c;我们讲解了十分重要的知识&#xff0c;在文件被打…

成集云 | 用友NC集成聚水潭ERP(用友NC主管供应链)| 解决方案

源系统成集云目标系统 方案介绍 用友NC是用友NC产品的全新系列&#xff0c;是面向集团企业的世界级高端管理软件。它以“全球化集团管控、行业化解决方案、全程化电子商务、平台化应用集成”的管理业务理念而设计&#xff0c;采用J2EE架构和先进开放的集团级开发平台…

平板用的触控笔什么牌子好?性价比高的触控笔推荐

随着平板电脑的普及&#xff0c;越来越多用户为了方便都选择了电容笔&#xff0c;电容笔已经完全代替了我们的手指&#xff0c;并且使我们的书写速度得到了极大的提升。然而&#xff0c;因为其的独特的重力压感功能与芯片技术&#xff0c;导致了原装笔的售价一直居高不下&#…

JavaScript - canvas - 放大镜

效果 示例 项目结构&#xff1a; 源码&#xff1a; <!DOCTYPE html> <html><head><meta charset"utf-8" /><title>放大镜</title><style type"text/css">div {width: 200px;height: 200px;display: inline-bl…

2023-09-20 Android CheckBox 让文字显示在选择框的左边

一、CheckBox 让文字在选择框的左边 &#xff0c;在布局文件里面添加下面一行就可以。 android:layoutDirection"rtl" 即可实现 android:paddingStart"10dp" 设置框文间的间距 二、使用的是left to right <attr name"layoutDirection">&…

RGB格式

Qt视频播放器实现&#xff08;目录&#xff09; RGB的使用场景 目前&#xff0c;数字信号源&#xff08;直播现场的数字相机采集的原始画面&#xff09;和显示设备&#xff08;手机屏幕、笔记本屏幕、个人电脑显示器屏幕&#xff09;使用的基本上都是RGB格式。 三原色 RGB是…

网络协议层次模型

OSI 七层模型 在 OSI 网络分层模型中&#xff0c;每个分层都接收由它下一层所提供的特定服务&#xff0c;并且负责为自己的上一层提供特定的服务。上下层之间进行交互时所遵循的约定叫做接口 &#xff1b;同一层之间的交互所遵循的约定叫做协议&#xff0c; 下图是 OSI 七层…

2023华为杯研究生数学建模竞赛CDEF题思路+模型代码

全程更新华为杯研赛CDEF题思路模型及代码&#xff0c;大家查看文末名片获取 华为杯C题思路分析 问题一 在每个评审阶段&#xff0c;作品通常都是随机分发的&#xff0c;每份作品需要多位评委独立评审。为了增加不同评审专家所给成绩之间的可比性&#xff0c;不同专家评审的作…

SpringBoot轻松实现项目集成Knife4j接口文档

Knife4j 介绍 Knife4j 官网 Knife4j是一款基于Swagger生成API文档的增强工具&#xff0c;它简化了开发者构建和管理RESTful API文档的过程。通过自动扫描项目中的接口信息&#xff0c;Knife4j能够生成详细、易读的API文档&#xff0c;无需手动编写和维护。它提供交互式的接口调…

CSP-J/S 2023第一轮认证晋级分数线有些爆冷,超出想象

昨天广东、江西、内蒙古、河北、江苏、吉林、四川相继公布了第一轮认证成绩和晋级分数线。 爆冷 说实在话&#xff0c;看到这个分数线&#xff0c;我有些意外。 省份 CSP-J CSP-S 江西 55 29 内蒙古 30 20 河北 48 20 江苏 72.5 52.5 吉林 52 33 四川 67 55 就拿我们大河北来…

02强化学习基本概念

强化学习基本概念 前言1、State、Action、Policy等① State② Action③ State transition④ State transition probability⑤ Polity 2、Reward、Return、MDP等① Reward② Trajectory and return③ Discounted return④ Episode⑤ MDP 总结&#xff1a; 前言 本文来自西湖大学…

一、荔枝派nano启动USB虚拟串口功能FLASH版

一、搭建环境 本次使用的是ubuntu16.04进行搭建编译环境&#xff0c;具体的配置编译登都在荔枝派官网有介绍&#xff1a;https://wiki.sipeed.com/soft/Lichee/zh/Nano-Doc-Backup/build_sys/build_flash_copy.html 特别注意&#xff1a;本次需要使用的USB转串口功能&#xff0…

解决方案 | 如何构建市政综合管廊安全运行监测系统?

如何构建市政综合管廊安全运行监测系统&#xff1f;WITBEE万宾城市生命线智能监测仪器&#xff0c;5年免维护设计&#xff0c;集成10多项结构与气体健康监测指标&#xff0c;毫秒级快速响应&#xff0c;时刻感知综合管廊运行态势

【kafka】可视化工具KAFKA EAGLE安装分享

目录 准备&#xff1a; 开始&#xff1a; 1.解压 2.环境变量配置 3.生效环境变量配置文件 3.修改配置文件 1.修改zookeeper集群信息 2.修改mysql配置信息 4.启动 5.异常排查 6.页面 创作不易&#xff0c;你的动力是我创作的动力&#xff0c;如果有帮助请关注我&…

C 初级学习笔记(基础)

目录 1.预处理器指令 预定义宏 预处理器运算符 &#xff08;\&#xff09; 参数化的宏 头文件 .h 引用头文件操作 2.函数&#xff08;标识符&关键字&运算符&#xff09;存储类 函数参数 a. 标识符&关键字 b. 运算符&#xff08;算术、关系、逻辑、位、赋…

千巡翼X1 让航测无人机更小更轻更高效

利用无人机进行航空摄影测量&#xff0c;已成为测绘外业生产的主要方式&#xff0c;不仅方便快捷&#xff0c;更能全面准确获得成果。近年来&#xff0c;凭借快速高效、机动灵活、安全可靠、低成本等诸多优势&#xff0c;小型多旋翼无人机逐渐成为一些航测项目作业的新利器。 千…

方案:基于AI烟火识别与视频技术的秸秆焚烧智能化监控预警方案

一、方案背景 为严控秸秆露天焚烧&#xff0c;改善环境空气质量&#xff0c;各省相继发布秸秆禁烧工作内容。以安徽省为例&#xff0c;大气污染防治联席会议下发了该省2020年秸秆禁烧工作部署通知。2020年起&#xff0c;气象局将对全省秸秆焚烧火点实施卫星全年全时段监测&…

C语言入门Day_24 函数与指针

目录 前言&#xff1a; 1.指针和数组 2.函数和指针 3.易错点 4.思维导图 前言&#xff1a; 我们知道数组是用来存储多个数据的&#xff0c;以及我们可以用指针来指向一个变量。那么我们可以用指针来指向一个数组中的数据么&#xff1f; 指针除了可以像指向一个变量一样指…