• Spring Boot+Redis实现用户登录验证的思路


    一、为什么进行登录验证

    进行登录验证的作用,是为了保护数据,在用户没有进行登录时有些数据和操作是不能被授权使用的。例如,购物商城里的评论,未登录时可以看评论但是不能写评论。也不可以买东西。这就要求我们进行每一个请求的登录验证了。

    二、实现思路

    实现登录验证我知道的方法有两种,第一种是存入session中,但是session只能保证一次会话有效,就是说在同一个Tomcat中有效,但是我们为了减少服务器的压力,通常会配置一个tomcat的集群,这时一个tomcat中session可以在另一个tomcat中被查询到吗?肯定是不行的。
    所以,有了第二种方法,存入redis中。每个tomcat服务都可以通过redis进行校验。具体过程如下(前面还有验证码校验省略了,道理一样):

    1.首先进行用户登录,先在数据库中查,如果存在用户则将用户信息存入redis中,并设置有效时间:

    //                   修改: 将信息存入redis中,并随机生成token登录令牌,将User对象转为hash格式存储
    //                    随机生成UUID作为token
                        String token = UUID.randomUUID().toString(true);
                        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
    
    //                    将userDTO转成hashMap,userDTO中的Long类型不能转化为String,所以需要我们自己进行转化
                        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
                                CopyOptions.create()
                                        .setIgnoreNullValue(true)
    //                                    将map中的值都变成了字符串类型
                                        .setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));
    
                        HashOperations<String, Object, Object> stringObjectObjectHashOperations = stringRedisTemplate.opsForHash();
                        stringObjectObjectHashOperations.putAll(LOGIN_USER_KEY + token, userMap);
    //                    设置有效时间,问题:该方式说明无论你是否操作一但过了30分钟,就会被认定为未登录,所以我们应该在拦截器中设置每次操作更新token的存活时间
                        stringRedisTemplate.expire(LOGIN_USER_KEY + token, LOGIN_USER_TTL, TimeUnit.MINUTES);
    //                    返回token给浏览器
                        return Result.ok(token);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    2.如果在数据库中没有说明第一次登录,则直接进行注册

     private User createUserWithPhone(String phone) {
            User user = new User();
            user.setPhone(phone);
            user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(6));
            this.save(user);
            return user;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    三、出现的问题

    1.设置了有效时间为30分钟实现,但是这样写表示无论怎样操作我们用户登录验证的有效时间只有30分钟,时间一过我们就会被强制登出。
    2.如何实现对请求的登录验证呢?

    四、解决问题

    问题1
          每次请求操作我们都更新一次redis中用户信息的存活时间
    
    • 1
    //            新增刷新token的有效期
            stringRedisTemplate.expire(LOGIN_USER_KEY + token, LOGIN_USER_TTL, TimeUnit.MINUTES);
    
    • 1
    • 2
    问题2:
         我们可以自定义拦截器,进行路径的拦截,将自定义拦截器,加入到springMVC的默认拦截器中,并且可以通过order设置使用顺序,数值越小越先使用:
    
    • 1
    @Configuration
    public class SpringMVCConfig implements WebMvcConfigurer {
        @Autowired
        StringRedisTemplate redisTemplate;
        /**
         * @Description 添加自定义的拦截器,并排除不需要拦截的请求
         * @Retrurn
         */
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
    //       TODO: 暂时将upload放行,方便测试
    //        登录拦截器
            registry.addInterceptor(new LoginInterceptor())
                    .excludePathPatterns(
                            "/user/code",
                            "/user/login",
                            "/blog/hot",
                            "/shop/**",
                            "/shop-type/**",
                            "/upload/**",
                            "/voucher/**"
                    ).order(1);
    //        token刷新拦截器
            registry.addInterceptor(new RefreshtokenInterceptor(redisTemplate)).order(0);
        }
    }
    
    • 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

    五、对两个拦截器的说明

    一、LoginInterceptor
    主要用来进行登录验证的拦截

    public class LoginInterceptor implements HandlerInterceptor {
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    //        只用进行判断是否需要拦截,依据就是ThreadLocal中是否存在用户,有就放行,没有就拦截
            UserDTO user = UserHolder.getUser();
            if (user == null){
                response.setStatus(401);
                return false;
            }
    //        放行
            return true;
        }
    
        /**
         * @Description 后置拦截器,前置拦截器放行后,删除session保护用户信息
         * @Retrurn
         */
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            UserHolder.removeUser();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    二、RefreshtokenInterceptor
    在登录拦截器前进行全路径拦截,为了更新redis中的用户信息存活时间,以及将用户信息存入ThreadLocal中便于使用。

    public class RefreshtokenInterceptor implements HandlerInterceptor {
        private StringRedisTemplate stringRedisTemplate;
    
        public RefreshtokenInterceptor(StringRedisTemplate stringRedisTemplate) {
            this.stringRedisTemplate = stringRedisTemplate;
        }
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            //        获取请求头中的token
            String token = request.getHeader("authorization");
            boolean blank = StrUtil.isBlank(token);
    //        是空表示没有登录,放行交给登录拦截器
            if (blank) {
                return true;
            }
    //        基于token获取redis中的用户,entries可以接受一个键值中的所有字段
            Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(LOGIN_USER_KEY + token);
    //        取不到也放行,该拦截器只是为了更新redis和保存ThreadLocal
            if (userMap.isEmpty()) {
                return true;
            }
    //        如果不为空,则将usermap在转换成userDTO
            UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
    //        存在放行,并且保存在ThreadLocal中
            UserHolder.saveUser(userDTO);
    
    //            新增刷新token的有效期
            stringRedisTemplate.expire(LOGIN_USER_KEY + token, LOGIN_USER_TTL, TimeUnit.MINUTES);
    //        放行
            return true;
        }
    }
    
    
    • 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
    • 30
    • 31
    • 32
    • 33
    • 34
  • 相关阅读:
    Linux扩展篇之Shell编程一(Shell 概述)
    2022年12月英语六级预测范文:情景作文-流行文化
    订水商城H5实战教程-01需求分析
    机器学习8线性回归法Linear Regression
    【系统设计系列】异步和网络通信
    剑指 Offer (更新中)
    微信小程序在线预览PDF文件
    2024目前三种有效加速国内Github
    查找xml文件
    CV计算机视觉每日开源代码Paper with code速览-2023.10.13
  • 原文地址:https://blog.csdn.net/m0_45101736/article/details/126222159