• 【Java Web】用Redis优化登陆模块


    • 使用Redis存储验证码
      • 验证码需要频繁访问和封信,对性能要求高;
      • 验证码不需要永久保存,通常在很短时间内失效;
      • 分布式部署,存在Session共享问题;
    • 使用Redis存储登陆凭证
      • 处理每次请求时,都要查询用户登陆凭证,访问频率非常高;
    • 使用Redis缓存用户信息
      • 处理每次请求时,都要根据凭证查询用户信息,访问频率非常高。
        • 查询缓存中是否有user;
        • 如果没有就初始化缓存;
        • 如果user的信息更新,将清除缓存;

    1. 使用Redis存储验证码

    • 在生成验证码的逻辑中,owner由UUID生成,并交给Cookie;然后将验证码text存到kaptcha:owner键中,并设置有效时间为60秒
    @RequestMapping(path="/kaptcha", method = RequestMethod.GET)
        public void getKaptcha(HttpServletResponse response, HttpSession session){
            // 生成验证码
            String text = kaptchaProducer.createText();
            BufferedImage image = kaptchaProducer.createImage(text);
    
            // 将验证码存入session
            // session.setAttribute("kaptcha", text);
            // 优化:存到redis里
            // 验证码的归属owner
            String kaptchaOwner = CommunityUtil.generateUUID();
            Cookie cookie = new Cookie("kaptchaOwner",kaptchaOwner);
            cookie.setMaxAge(60);
            cookie.setPath(context_path);
            response.addCookie(cookie);
    
            String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
            redisTemplate.opsForValue().set(redisKey, text, 60, TimeUnit.SECONDS);
    
            // 将图片输出给浏览器
            response.setContentType("image/png");
            try{
                OutputStream os = response.getOutputStream();
                ImageIO.write(image, "png", os);
            } catch (IOException e) {
                logger.error("响应验证码失败:"+e.getMessage());
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 在登陆功能中,键从cookie中去哪出,然后从redis中获取kaptcha:kaptchaOwner键对应的值(验证码),判断验证码是否正确;
    @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){
            // 从Session中取并检查验证码 --> 优化:从Redis中取
            // 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.equals(code)){
                model.addAttribute("codeMsg","验证码不正确");
                return "/site/login";
            }
            ......
            ......
            ......
            ......
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    2. 使用Redis存储登陆凭证

    各个逻辑验证通过后,将生成一个用户凭证(UUID生成),存到Redis中,并设置生存周期。在后续需要验证登陆逻辑时,从Redis中get即可。

     public Map<String,Object> login(String username, String password, int expiredSeconds){
            Map<String,Object> map = new HashMap<>();
            // 空值判断
            ....
            ....
            
            // 账号密码都不为空,验证合法性
            // 验证账号合法性
            .....
            .....
            
            // 验证密码
            ....
            ....
            
            // 生成登陆凭证
            LoginTicket loginTicket = new LoginTicket();
            loginTicket.setUserId(user.getId());
            loginTicket.setTicket(CommunityUtil.generateUUID());
            loginTicket.setStatus(0);
            loginTicket.setExpired(new Date(System.currentTimeMillis() + 1000 * 60 * 10));
            loginTicketMapper.insertLoginTicket(loginTicket);
    
            String redisKey = RedisKeyUtil.getTicketKey(loginTicket.getTicket());
            redisTemplate.opsForValue().set(redisKey,loginTicket);
    
            map.put("ticket", loginTicket.getTicket());
            return map;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    3. 使用Redis缓存用户信息

    // 1. 优先从缓存中取值
        public User getCache(int userId){
            String redisKey = RedisKeyUtil.getUserKey(userId);
            return (User) redisTemplate.opsForValue().get(redisKey);
        }
        // 2. 如果取不到就初始化缓存
        public User initCache(int userId){
            User user = userMapper.selectById(userId);
            String redisKey = RedisKeyUtil.getUserKey(userId);
            redisTemplate.opsForValue().set(redisKey, user, 3600, TimeUnit.SECONDS);
            return user;
        }
    
        // 3. 数据变更时清除缓存数据
        public void clearCache(int userId){
            String redisKey = RedisKeyUtil.getUserKey(userId);
            redisTemplate.delete(redisKey);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 当获取user的时候,例如根据userId获取用户信息,先判断缓存中查有没有user:userId这个键。如果有,就从缓存中返回User;如果没有,就初始化缓存,将User信息写入user:userId。
    • 每次用户信息修改后,例如更改密码后、退出登录修改ticket后、修改用户激活状态后等,多需要做一次clearCache。
  • 相关阅读:
    主流的网络计算模式有哪些
    cmake创建codeblocks项目
    揭露测试外包公司,关于外包,你或许听到过这样的声音
    思腾云计算
    JAVASE--继承
    C ++ 类 | 类与函数(Function)_5
    linux 中文乱码 解决方法
    如何实现购物车一键全选?
    Mybatis 拦截器(Mybatis插件原理)
    猿创征文|Spring Boot 整合分布式调度框架:xxl-job
  • 原文地址:https://blog.csdn.net/qq_42251120/article/details/132779956