1、Redis存储验证码
原来是用session存验证码,现在用Redis存。
-
首先服务器生成验证码:给某客户端生成一个随机字符串,存在客户端的cookie里。将随机字符串当作redis的key,value存生成的字符串。且redis存储的时间仅60s
-
下次客户端输入了验证码,传给服务端:服务端取出cookie,即可到redis的key,根据key找到验证码,然后验证客户端输入的验证码code和redis里存的验证码是否一致,若一致则登录成功。
-
key存代表各客户端的随机字符串(在客户端cookie中)
-
value存验证码text
(1)RedisKeyUtil
private static final String PREFIX_KAPTCHA = "kaptcha";
// 登录验证码
public static String getKaptchaKey(String owner) {
return PREFIX_KAPTCHA + SPLIT + owner;
}
(2)LoginController
生成验证码GET getKaptcha
@RequestMapping(path = "/kaptcha", method = RequestMethod.GET)
public void getKaptcha(HttpServletResponse response/*, HttpSession session*/) {
// 生成验证码
String text = kaptchaProducer.createText();
BufferedImage image = kaptchaProducer.createImage(text);
// // 将验证码存入session(多个请求要用,这次生成,下次session)
// session.setAttribute("kaptcha", text);
// 生成验证码归属,将验证码存入Redis
String kaptchaOwner = CommunityUtil.generateUUID();
String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
redisTemplate.opsForValue().set(redisKey, text, 60, TimeUnit.SECONDS);
// 将验证码归属存入cookie
Cookie cookie = new Cookie("kaptchaOwner", kaptchaOwner);
cookie.setMaxAge(60);
cookie.setPath(contextPath);
response.addCookie(cookie);
// 将图片输出给浏览器(先告诉浏览器生成的是什么格式的图片,获取输出流,输出字节流(图片)-捕获异常)
response.setContentType("image/png");
try {
OutputStream os = response.getOutputStream();
ImageIO.write(image, "png", os);
} catch (IOException e) {
logger.error("响应验证码失败:" + e.getMessage());
}
}
登录POST,login
@RequestMapping(path = "/login", method = RequestMethod.POST)
public String login(String username, String password, String code, boolean rememberme,
Model model/*, HttpSession session*/, HttpServletResponse response,
@CookieValue("kaptchaOwner") String kaptchaOwner) {
// // 检查验证码
// String kaptcha = (String) session.getAttribute("kaptcha");
String kaptcha = null;
if (StringUtils.isNotBlank(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";
}
// 检查账号,密码
int expiredSeconds = rememberme ? REMEMBER_EXPIRED_SECONDS : DEFAULT_EXPIRED_SECONDS;
Map<String, Object> map = userService.login(username, password, expiredSeconds);
if (map.containsKey("ticket")) {
Cookie cookie = new Cookie("ticket", map.get("ticket").toString());
cookie.setPath(contextPath);
cookie.setMaxAge(expiredSeconds);
response.addCookie(cookie);
return "redirect:/index";
} else {
model.addAttribute("usernameMsg", map.get("usernameMsg"));
model.addAttribute("passwordMsg", map.get("passwordMsg"));
return "/site/login";
}
}
2、Redis存储登录凭证
每次请求,查询登录凭证
-
key存ticket
-
value存loginTicket对象,redis自动转成JSON字符串
(1) RedisKeyUtil
private static final String PREFIX_TICKET = "ticket";
// 登录的凭证
public static String getTicketKey(String ticket) {
return PREFIX_TICKET + SPLIT + ticket;
}
(2) LoginTicketMapper
加注解@Deprecated // 不推荐使用
(3) UserService
login方法(后两行)
// 生成登录凭证
LoginTicket loginTicket = new LoginTicket();
loginTicket.setUserId(user.getId());
loginTicket.setTicket(CommunityUtil.generateUUID());
loginTicket.setStatus(0);
loginTicket.setExpired(new Date(System.currentTimeMillis() + expiredSeconds * 1000));
// loginTicketMapper.insertLoginTicket(loginTicket);
String redisKey = RedisKeyUtil.getTicketKey(loginTicket.getTicket());
redisTemplate.opsForValue().set(redisKey, loginTicket);
logout方法
public void logout(String ticket) {
// loginTicketMapper.updateStatus(ticket, 1);
String redisKey = RedisKeyUtil.getTicketKey(ticket);
LoginTicket loginTicket = (LoginTicket) redisTemplate.opsForValue().get(redisKey);
loginTicket.setStatus(1);
redisTemplate.opsForValue().set(redisKey, loginTicket);
}
findLoginTicket方法,每次请求,拦截器都拦截
public LoginTicket findLoginTicket(String ticket) {
// return loginTicketMapper.selectByTicket(ticket);
String redisKey = RedisKeyUtil.getTicketKey(ticket);
return (LoginTicket) redisTemplate.opsForValue().get(redisKey);
}
3、Redis缓存用户信息
每次请求,根据登录凭证,查询用户信息
-
key存userId
-
value存user对象,redis自动转成JSON字符串
-
缓存3600s。查询先从缓存中查,如果没有,缓存从MySQL中取,缓存3600s。如果更改数据,清除缓存数据。
(1)RedisKeyUtil
private static final String PREFIX_USER = "user";
// 用户
public static String getUserKey(int userId) {
return PREFIX_USER + SPLIT + userId;
}
(2)UserService
缓存用户信息:
- 优先从缓存中取值
- 取不到,初始化缓存数据,设置存3600s,即一个小时
- 数据变更,清除缓存数据
// 1.优先从缓存中取值
private User getCache(int userId) {
String redisKey = RedisKeyUtil.getUserKey(userId);
return (User) redisTemplate.opsForValue().get(redisKey);
}
// 2.取不到,初始化缓存数据
private User initCache(int userId) {
String redisKey = RedisKeyUtil.getUserKey(userId);
User user = userMapper.selectById(userId);
redisTemplate.opsForValue().set(redisKey, user, 3600, TimeUnit.SECONDS);
return user;
}
// 3.数据变更,清除缓存数据
private void clearCache(int userId) {
String redisKey = RedisKeyUtil.getUserKey(userId);
redisTemplate.delete(redisKey);
}
public User findUserById(int id) {
// return userMapper.selectById(id);
User user = getCache(id);
if (user == null) {
user = initCache(id);
}
return user;
}
public int updateHeader(int userId, String headerUrl) {
// return userMapper.updateHeader(userId, headerUrl);
int rows = userMapper.updateHeader(userId, headerUrl);
clearCache(userId);
return rows;
}