今天给项目换了一个登录页面,而这个登录页面设计了验证码,于是想着把这个验证码功能实现一下吧。
这篇文章就如何实现登录时的验证码的验证功能结合代码进行详细地介绍,以及介绍功能实现的思路。
目录
页面效果
实现思路
前端页面代码
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);}}
好了,这篇文章就分享到这里了,看完如果觉得对你有所帮助,不要忘了点赞+收藏哦~