相关推荐recommended
【SpringBoot】Redis集中管理Session和自定义用户参数解决登录状态及校验问题
作者:mmseoamin日期:2024-02-18

【SpringBoot】Redis集中管理Session和自定义用户参数解决登录状态及校验问题,在这里插入图片描述,第1张


🏡浩泽学编程:个人主页

 🔥 推荐专栏:《深入浅出SpringBoot》《java对AI的调用开发》
              《RabbitMQ》《Spring》《SpringMVC》
🛸学无止境,不骄不躁,知行合一

文章目录

  • 前言
  • 一、分布式Session问题
    • Redis集中管理Session
    • 二、用户校验问题
      • 自定义用户参数
      • MVC拦截器
      • 总结

        前言

        主要讲解:Redis集中管理Session存储用户登录信息,解决分布式Session问题;自定义用户参数配合MVC拦截器实现控制层入参前进行用户校验,解决每层用户接口都要做用户校验问题。


        一、分布式Session问题

        在实现用户登录时,我们需要注意的就是就是用户权限带来的用户登录状态问题:在大多数项目中,应用采用Nginx反向代理,这会存在一种情况——用户信息在Tomcat1登录之后,用户信息放在Tomcat1的Session里,过一会,请求又被Nginx分发到Tomcat2上,这是Tomcat2上Session里还没有用户信息,于是又要登录。

        解决方案有很多:

        • Session复制
          • 优点
            • 无需修改代码,只需要修改Tomcat配置
            • 缺点
              • Session同步传输占用内网带宽
              • 多台Tomcat同步性能指数级下降
              • Session占用内存,无法有效水平扩展
              • 前端存储
                • 优点
                  • 不占用服务器端内存
                  • 缺点
                    • 存在安全风险
                    • 数据大小受到cookie限制
                    • 占用外网带宽
                    • Session粘滞
                      • 优点
                        • 无需修改代码
                        • 服务器端可以水平扩展
                        • 缺点
                          • 增加新机器,会重新Hash,导致重新登录
                          • 应用重启,需要重新登录

                            Redis集中管理Session

                            • 这里采用Redis集中管理所有Session,即多个地方从一个地方(Redis)中获取信息。当然大家也可以使用SpringSession实现分布式Session。
                            • 实现:登录时将用户信息存入Redis,这里只是实现了简单的集中储存用户信息,并没有
                            • 对于Redis集中管理Session,我在做黑马点评时记录过,很完善,可以看看:短信登录实现(黑马点评)

                            Redis配置类:键值对序列化

                            /**
                             * @Version: 1.0.0
                             * @Author: Dragon_王
                             * @ClassName: RedisConfig
                             * @Description: Redis配置类
                             * @Date: 2024/1/25 15:32
                             */
                            @Configuration
                            public class RedisConfig {
                                @Bean
                                public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
                                    RedisTemplate redisTemplate = new RedisTemplate<>();
                                    //key序列化
                                    redisTemplate.setKeySerializer(new StringRedisSerializer());
                                    //value序列化
                                    redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
                                    //hash的key序列化
                                    redisTemplate.setHashKeySerializer(new StringRedisSerializer());
                                    //hash的value序列化
                                    redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
                                    //注入连接工厂
                                    redisTemplate.setConnectionFactory(redisConnectionFactory);
                                    return redisTemplate;
                                }
                            }
                            

                            登录逻辑:

                            /**
                             * @Version: 1.0.0
                             * @Author: Dragon_王
                             * @ClassName: IUserServiceImpl
                             * @Description: 登录处理
                             * @Date: 2024/1/23 15:52
                             */
                            @Service
                            @Primary
                            public class IUserServiceImpl extends ServiceImpl implements IUserService {
                                @Autowired
                                private UserMapper userMapper;
                                @Autowired
                                private RedisTemplate redisTemplate;
                                /***
                                 * @Description: 登录
                                 * @param loginVo
                                 * @methodName: doLogin
                                 * @return: com.example.seckill.vo.RespBean
                                 * @Author: dragon_王
                                 * @Date: 2024-01-23 17:38:35
                                 */
                                @Override
                                public RespBean doLogin(LoginVo loginVo, HttpServletRequest request, HttpServletResponse response) {
                                    String mobile = loginVo.getMobile();
                                    String password = loginVo.getPassword();
                                    //根据手机号获取用户
                                    User user = userMapper.selectById(mobile);
                                    if (null == user) {
                                        throw new GlobalException(RespBeanEnum.LOGIN_ERROR);
                                    }
                                    //判断密码是否正确
                                    if (!MD5Util.formPassToDBPass(password,user.getSalt()).equals(user.getPassword())){
                                        throw new GlobalException(RespBeanEnum.LOGIN_ERROR);
                                    }
                                    //生成cookie
                                    String ticket = UUIDUtil.uuid();
                                    redisTemplate.opsForValue().set("user:" + ticket,user);
                                    CookieUtil.setCookie(request,response,"userTicker",ticket);
                                    return RespBean.success();
                                }
                                /***
                                 * @Description: 根据cookie获取用户
                                 * @param userTicker
                                 * @methodName: getUserByCookie
                                 * @return: com.example.seckill.pojo.User
                                 * @Author: dragon_王
                                 * @Date: 2024-01-25 16:03:14
                                 */
                                @Override
                                public User getUserByCookie(String userTicker,HttpServletRequest request,HttpServletResponse response) {
                                    if (StringUtils.isEmpty(userTicker)){
                                        return null;
                                    }
                                    User user = (User) redisTemplate.opsForValue().get("user:" + userTicker);
                                    if (user != null) {
                                        CookieUtil.setCookie(request,response,"userTicker",userTicker);
                                    }
                                    return user;
                                }
                            }
                            

                            主要看如下代码:

                            //在账号密码正确后随机生成UUID,将 “user”+UUID 作为用户信息唯一key值,并存入redis中,这里我的cookie里存了一份UUID,因为Cookie存在于服务器或本地,不同于Session(只能存在于服务器),在使用应用程序时,不管请求在哪个tomcat,而用户在自己浏览器或本地上的信息能获取到。CookieUtil是自定义封装的cookie存取的工具类。
                              String ticket = UUIDUtil.uuid();
                              redisTemplate.opsForValue().set("user:" + ticket,user);
                              CookieUtil.setCookie(request,response,"userTicker",ticket);
                              return RespBean.success();
                            //如果Cookie里没有就说明用户没登陆过,因为我在登录时已经存过当前用户随机生成的UUID作为Cookie,没有的话就会返回空,有就根据当前用户UUID获取存在redis中的序列化的用户信息
                             public User getUserByCookie(String userTicker,HttpServletRequest request,HttpServletResponse response) {
                                    if (StringUtils.isEmpty(userTicker)){
                                        return null;
                                    }
                                    User user = (User) redisTemplate.opsForValue().get("user:" + userTicker);
                                    if (user != null) {
                                        CookieUtil.setCookie(request,response,"userTicker",userTicker);
                                    }
                                    return user;
                                }
                            

                            在黑马点评实现登录时,关于分布式Session,用到了token刷新:就是在用户登录时将用户信息存储到redis中,所谓token值,使用的就是UUID,同时token也作为redis中的key值,并且设置过期时间,但是这里有个刷新机制,就是设置拦截器——当用户访问某个页面时就自动刷新过期时间,使得如果用户一直在操作就不会突然过期,详细看那篇文章,这里不再补充。

                            二、用户校验问题

                            对于用户操作,会有权限限制即判断用户是否登录,如果每层用户业务接口都做用户校验会太过麻烦,所以可以自定义用户参数,在每次controller层入参之前就去做拦截校验。

                            自定义用户参数

                            /**
                             * @Version: 1.0.0
                             * @Author: Dragon_王
                             * @ClassName: UserArgumentResolve
                             * @Description: 自定义用户参数
                             *              获取用户是否登录
                             * @Date: 2024/1/25 16:31
                             */
                            @Component
                            public class UserArgumentResolve implements HandlerMethodArgumentResolver {
                                @Autowired
                                private IUserService userService;
                                @Override
                                public boolean supportsParameter(MethodParameter parameter) {
                                    Class parameterType = parameter.getParameterType();
                                    return parameterType == User.class;
                                }
                                @Override
                                public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
                                    HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
                                    HttpServletResponse response = webRequest.getNativeRequest(HttpServletResponse.class);
                                    String ticker = CookieUtil.getCookieValue(request, "userTicker");
                                    if (StringUtils.isEmpty(ticker)){
                                        return null;
                                    }
                                    return userService.getUserByCookie(ticker,request,response);
                                }
                            }
                            

                            解释:supportsParameter函数判断参数类型是否为User类型,是的的话执行resolveArgument函数,resolveArgument函数则会先查询当前cookie里是否有用户信息,没有的话返回空,有的话返回用户通过校验。

                            MVC拦截器

                            /**
                             * @Version: 1.0.0
                             * @Author: Dragon_王
                             * @ClassName: WebConfig
                             * @Description: MVC配置类
                             * @Date: 2024/1/25 16:27
                             */
                            @Configuration
                            @EnableWebMvc
                            public class WebConfig implements WebMvcConfigurer {
                                @Autowired
                                private UserArgumentResolve userArgumentResolve;
                                @Override
                                public void addArgumentResolvers(List resolvers) {
                                    resolvers.add(userArgumentResolve);
                                }
                            }
                            

                            这样设置后,以后Controller层接口传参只需要有User类型对象,自动校验用户状态,判断用户是否属于登录状态。


                            总结

                            以上就是Redis集中管理Session存储用户登录信息,解决分布式Session问题;自定义用户参数配合MVC拦截器实现控制层入参前进行用户校验,解决每层用户接口都要做用户校验问题的讲解。