在登录界面,主要实现两个功能,刷新验证码和记住我功能

验证码
产生验证码
使用kaptcha生成验证码,导入jar包,编写对应的config配置类,然后在controller中生成验证码。新建cookie,在cookie中放入验证码的uuid,将cookie作为key存入redis,把text作为value,在添加过期时间。
通过response.addCookie(cookie)把cookie发送给前端,OutputStream os = response.getOutputStream(); ImageIO.write(image,“png”,os);将图片输出到浏览器。
@GetMapping("/kaptcha")
public void getKaptcha(HttpServletResponse response){
// 生成验证码
String text = kaptchaProducer.createText();
BufferedImage image = kaptchaProducer.createImage(text);
// 验证码归属
String kaptchaOwner = CommunityUtil.generateUUID();
Cookie cookie = new Cookie("kaptchaOwner", kaptchaOwner);
cookie.setMaxAge(60);
cookie.setPath(contextPath);
response.addCookie(cookie);
// 存入Redis
String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
redisTemplate.opsForValue().set(redisKey,text,60, TimeUnit.SECONDS);
// 将图片输出给浏览器
try{
OutputStream os = response.getOutputStream();
ImageIO.write(image,"png",os);
} catch (IOException e) {
LOGGER.error("响应验证码失败:" + e.getMessage());
}
}
验证验证码
// @CookieValue("kaptchaOwner") String kaptchaOwner
String kaptcha = null;
// cookie不为空,通过redis的key得到value
if(StringUtils.isNoneBlank(kaptchaOwner)){
String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
kaptcha = (String) redisTemplate.opsForValue().get(redisKey);
}
// 验证码错误等情况
if(StringUtils.isBlank(kaptcha) || StringUtils.isBlank(code) || !kaptcha.equalsIgnoreCase(code)){
model.addAttribute("codeMsg","验证码不正确!");
return "site/login";
}
保存登录状态
默认状态的登录凭证的超时时间 12小时,记住状态下的登录凭证超时时间 100天。
service层:
// 生成登录凭证
LoginTicket loginTicket = new LoginTicket();
loginTicket.setUserId(user.getId());
loginTicket.setTicket(CommunityUtil.generateUUID());
loginTicket.setStatus(0);
loginTicket.setExpired(new Date(System.currentTimeMillis() + expiredSeconds * 1000));
String redisKey = RedisKeyUtil.getTicketKey(loginTicket.getTicket());
// loginTicket会序列化为json
redisTemplate.opsForValue().set(redisKey, loginTicket);
map.put("ticket", loginTicket.getTicket());
return map;
controller层:
// 是否记住我
int expiredSeconds = rememberme ? REMEMBER_EXPIRED_SECONDS : DEFAULT_EXPIRED_SECONDS;
Map<String, Object> map = userService.login(username, password, expiredSeconds);
String key = "ticket";
if(map.containsKey(key)){
// 新建cookie,配置cookie
Cookie cookie = new Cookie("ticket", map.get("ticket").toString());
cookie.setPath(contextPath);
cookie.setMaxAge(expiredSeconds);
response.addCookie(cookie);
return "redirect:/";
} else{
// 返回错误信息
model.addAttribute("usernameMsg",map.get("usernameMsg"));
model.addAttribute("passwordMsg",map.get("passwordMsg"));
return "site/login";
}
自动登录实现
先封装两个小工具类
要从cookie中拿到ticket的值,先封装一个cookieUtil:
public class CookieUtil {
public static String getValue(HttpServletRequest request, String name) {
if (request == null || name == null) {
throw new IllegalArgumentException("参数为空!");
}
Cookie[] cookies = request.getCookies();
// 遍历cookie,找到我们想要的那个
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals(name)) {
return cookie.getValue();
}
}
}
return null;
}
}
浏览器同时处理多个用户请求,对每个用户的user对象要做一个存储,可以使用session,但是session依赖servlet api,我们想要在方法里随用随取,为了解决这个问题,我们就要采取一种新的方法来存储用户信息——ThreadLocal。
ThreadLocal,顾名思义,就是本地线程,可是这个名字实在容易让人误解,因为其实它是本地线程局部变量的意思,首先我们要知道,我们每个请求都会对应一个线程,这个ThreadLocal就是这个线程使用过程中的一个变量,该变量为其所属线程所有,各个线程互不影响。
我们封装一个工具HostHolder,用来持有用户信息,代替session对象。主要有添加查询和删除方法,使多用户登录不冲突。
@Component
public class HostHolder {
private ThreadLocal<User> users = new ThreadLocal<>();
public void setUser(User user) {
users.set(user);
}
public User getUser() {
return users.get();
}
public void clear() {
users.remove();
}
}
首先重写preHandler方法,利用前面封装的cookieUtil工具得到ticket,查到用户,并用hostHolder持有用户。
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 从cookie中获取凭证
String ticket = CookieUtil.getValue(request, "ticket");
if (ticket != null) {
// 查询凭证
LoginTicket loginTicket = userService.findLoginTicket(ticket);
// 检查凭证是否有效
if (loginTicket != null && loginTicket.getStatus() == 0 && loginTicket.getExpired().after(new Date())) {
// 根据凭证查询用户
User user = userService.findUserById(loginTicket.getUserId());
// 在本次请求中持有用户
hostHolder.setUser(user);
// 构建用户认证的结果,并存入SecurityContext,以便Security进行授权
Authentication authentication =
new UsernamePasswordAuthenticationToken(
user, user.getPassword(), userService.getAuthorities(user.getId())
);
SecurityContextHolder.setContext(new SecurityContextImpl(authentication));
}
}
return true;
}
在模板视图上显示用户数据
然后重写postHandle方法,在modelAndView上加上user。
@Override
public void postHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
User user = hostHolder.getUser();
if (user != null && modelAndView != null) {
modelAndView.addObject("loginUser", user);
}
}
在请求结束时清理用户数据
@Override
public void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
hostHolder.clear();
}
本文详细介绍了如何在Spring Boot项目中使用Spring Security实现登录界面的验证码功能,包括生成与验证验证码的步骤。同时,文章讲解了登录状态的保存,包括默认12小时和记住我状态下的100天超时设置。最后,通过Cookie和ThreadLocal实现自动登录,包括在每次请求前获取用户信息、在模板视图中显示用户数据以及在请求结束后清理用户数据的过程。

2888

被折叠的 条评论
为什么被折叠?



