首页 > 其他分享 >优化登录模块(二十三)

优化登录模块(二十三)

时间:2022-09-19 22:13:09浏览次数:52  
标签:map return 二十三 登录 验证码 redisKey user 模块 String

一、使用Redis存储验证码

  • 验证码需要频繁的访问与刷新,对性能要求较高。
  • 验证码不需永久保存,通常在很短的时间后就会失效。
  • 分布式部署时,存在Session共享的问题。

RedisKeyUtil.java

    // 验证码前缀
    private static final String PREFIX_KAPTCHA = "kaptcha";
    // 登录验证码
    // 因为不知道具体用户,所以不能传userId
    public static String getKaptchaKey(String owner) {
        return PREFIX_KAPTCHA + SPLIT + owner;
    }

LoginController.java重构getKaptcha方法,将验证码存入Redis,key通过Cookie传给客户端。

    @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);

        // 验证码的归属
        String kaptchaOwner = CommunityUtil.generateUUID();
        // 通过cookie将归属传给客户端
        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);

        // 将图片输出给浏览器
        response.setContentType("image/png");
        try {
            OutputStream os = response.getOutputStream();
            ImageIO.write(image, "png", os);
        } catch (IOException e) {
            logger.error("响应验证码失败:" + e.getMessage());
        }
    }

重构Login方法,从Redis中读取验证码,key从Cookie中取。

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

Redis中存储的验证码。
image
image

二、使用Redis存储登录凭证

  • 处理每次请求时,都要查询用户的登录凭证,访问的频率非常高。
    RedisKeyUtil.java
    // 登录凭证前缀
    private static final String PREFIX_TICKET = "ticket";
    // 登录的凭证
    public static String getTicketKey(String ticket) {
        return PREFIX_TICKET + SPLIT + ticket;
    }

之前的LoginTicketMapper.java可以弃用。将LoginTicket存入到Redis中而不是数据库中。
重构loginlogoutfindLoginTicket方法。

    public Map<String, Object> login(String username, String password, int expiredSeconds) {
        Map<String, Object> map = new HashMap<>();
        if (StringUtils.isBlank(username)) {
            map.put("usernameMsg", "账号不能为空!");
            return map;
        }
        if (StringUtils.isBlank(password)) {
            map.put("passwordMsg", "密码不能为空");
            return map;
        }
        //查询账号
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
        lqw.eq(User::getUsername, username);
        User user = getOne(lqw);
        //验证账号
        if (user == null) {
            map.put("usernameMsg", "该账号不存在!");
            return map;
        }
        //验证状态
        if (user.getStatus() == 0) {
            map.put("usernameMsg", "该账号未激活!");
            return map;
        }
        //验证密码
        password = CommunityUtil.md5(password + user.getSalt());
        if (!password.equals(user.getPassword())) {
            map.put("passwordMsg", "密码错误!");
            return map;
        }
        //生成登录凭证
        LoginTicket loginTicket = new LoginTicket();
        loginTicket.setUserId(user.getId());
        loginTicket.setTicket(CommunityUtil.generateUUID());
		loginTicket.setStatus(0);
        loginTicket.setExpired(new Date(System.currentTimeMillis() + expiredSeconds * 1000));
//        loginTicketMapper.insert(loginTicket);
        // loginTicket存入到Redis中,会序列化为json字符串
        String redisKey = RedisKeyUtil.getTicketKey(loginTicket.getTicket());
        redisTemplate.opsForValue().set(redisKey, loginTicket);
        map.put("ticket", loginTicket.getTicket());
        return map;
    }
	
    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);
    }
	
     public LoginTicket findLoginTicket(String ticket) {
//        return loginTicketMapper.selectByTicket(ticket);
        String redisKey = RedisKeyUtil.getTicketKey(ticket);
        return (LoginTicket) redisTemplate.opsForValue().get(redisKey);
    }

Redis中的登录凭证
image

三、使用Redis缓存用户信息

  • 处理每次请求时,都要根据凭证查询用户信息,访问的频率非常高。
  1. 先尝试在缓存中取值
  2. 取到返回,取不到就初始化缓存数据
  3. 改动用户数据后,要更新缓存,方便起见可以直接删除缓存数据
    RedisKeyUtil.java
    // 用户前缀
    private static final String PREFIX_USER = "user";
    // 用户
    public static String getUserKey(int userId) {
        return PREFIX_USER + SPLIT + userId;
    }

重构UserServiceImpl.java的部分方法(示例)

    @Override
    public User findUserById(int id) {
        //return getById(id);
        User user = getCache(id);
        if (user == null) {
            user = initCache(id);
        }
        return user;
    }
    // 1.优先从缓存中取值
    private User getCache(int userId) {
        String redisKey = RedisKeyUtil.getUserKey(userId);
        return (User) redisTemplate.opsForValue().get(redisKey);
    }

    // 2.取不到时初始化缓存数据
    private User initCache(int userId) {
        User user = baseMapper.selectById(userId);
        String redisKey = RedisKeyUtil.getUserKey(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);
    }

缓存的用户信息
image

标签:map,return,二十三,登录,验证码,redisKey,user,模块,String
From: https://www.cnblogs.com/dalelee/p/16701187.html

相关文章

  • python利用logging模块实现根据日志级别打印不同颜色日志
    logger:日志器对象,可通过logging.getLogger()方法获取handler:处理器对象,将日志信息输出到指定位置,可通过logger.addHandler()方法进行添加formatter:格式器对象,输出格式化......
  • 使用J2EE 登录实例开发
    我们先了解下Servlet的生命周期Servlet部署在容器里,其生命周期由容器管理。概括为以下几个阶段:1)容器加载Servlet类。当第一次有Web客户请求Servlet服务或当W......
  • Python多线程编程——threading模块
    本文参考:https://blog.csdn.net/youngwyj/article/details/124720041https://blog.csdn.net/youngwyj/article/details/124833126目录前言threading模块1.简介2.创建线......
  • MariaDB免密码登录
    第一步:编辑配置文件vim/etc/my.cnf.d/server.cnf第二步:在mysqld下加入skip-grant-tables第三步:重启数据库systemctlrestartmariadb第四步:登录数据库mysql第五......
  • GitHub不再支持密码验证解决方案:SSH免密与Token登录配置
    今天提交代码,push到GitHub上,突然出现这个问题。remote:SupportforpasswordauthenticationwasremovedonAugust13,2021.Pleaseuseapersonalaccesstokenins......
  • Delphi WebBrowser完成网页自动登录
    订制一些WEB数据抓取项目时,会遇到需要先登录网站系统,这时候我们通常会做成由系统自动登录的方式。分析网页要完成这项功能,首先需要对项目的网页进行分析,找到关键数据项的......
  • time模块互相转换
    1.今日模块的介绍timedatatimeossyshashlib(加密)json(序列化)pickle(反序列化)cellections集合importtime获取时间戳:从时间元年19701.100:00:00到现在经过的秒数time.......
  • 12.ansible lineinfile模块
    前言:lineinfile模块,功能有点类似sed常用功能:对文件的行替换、插入、删除PS:替换/插入:如果有重复的,都是匹配最后一个,如果不加backrefs项,替换/插入如无匹配者,则将line......
  • 利用cpolar远程登录公司OA系统
    随着信息化办公的快速推进,很多企业已经用上了OA系统,并且我们的日常工作几乎都要依靠OA系统完成。虽然OA体统的广泛应用带来了不少工作上的便利,但从某种程度上形成了限制,毕......
  • 一文搞懂用户登录验证流程(附图)
    前言本文通过图示及代码的方式介绍用户登录流程及技术实现,内容包括用户登录,用户验证,如何获取操作用户的信息以及一些黑名单及匿名接口如何免验证相关的实现。结合网关......