【SpringBoot】Redis集中管理Session和自定义用户参数解决登录状态及校验问题

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 【SpringBoot】Redis集中管理Session和自定义用户参数解决登录状态及校验问题



前言

主要讲解: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<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<String,Object> 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<UserMapper, User> 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<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(userArgumentResolve);
    }
}

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


总结

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

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
26天前
|
并行计算 Java 数据处理
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
124 0
|
27天前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
139 2
|
27天前
|
JSON NoSQL Java
springBoot:jwt&redis&文件操作&常见请求错误代码&参数注解 (九)
该文档涵盖JWT(JSON Web Token)的组成、依赖、工具类创建及拦截器配置,并介绍了Redis的依赖配置与文件操作相关功能,包括文件上传、下载、删除及批量删除的方法。同时,文档还列举了常见的HTTP请求错误代码及其含义,并详细解释了@RequestParam与@PathVariable等参数注解的区别与用法。
|
26天前
|
NoSQL Java Redis
shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
这篇文章介绍了如何使用Spring Boot整合Apache Shiro框架进行后端开发,包括认证和授权流程,并使用Redis存储Token以及MD5加密用户密码。
22 0
shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
|
21天前
|
NoSQL Redis API
限流+共享session redis实现
【10月更文挑战第7天】
34 0
|
27天前
|
前端开发 Java 数据库
springBoot:template engine&自定义一个mvc&后端给前端传数据&增删改查 (三)
本文介绍了如何自定义一个 MVC 框架,包括后端向前端传递数据、前后端代理配置、实现增删改查功能以及分页查询。详细展示了代码示例,从配置文件到控制器、服务层和数据访问层的实现,帮助开发者快速理解和应用。
|
JSON 前端开发 Java
SpringBoot自定义异常,优雅解决业务逻辑中的错误
SpringBoot自定义异常,优雅解决业务逻辑中的错误
6113 0
SpringBoot自定义异常,优雅解决业务逻辑中的错误
|
25天前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,包括版本兼容性、安全性、性能调优等方面。
126 1
|
9天前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。首先,创建并配置 Spring Boot 项目,实现后端 API;然后,使用 Ant Design Pro Vue 创建前端项目,配置动态路由和菜单。通过具体案例,展示了如何快速搭建高效、易维护的项目框架。
86 62
|
6天前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,帮助开发者提高开发效率和应用的可维护性。
20 2