zoukankan      html  css  js  c++  java
  • 集成spring-session利用redis来实现session共享(21)

    简介

    单机部署web应用的时候session是唯一的,但是如果水平扩展后,通过nginx负载访问,就会出现session不一致的情况,
    例如在A节点登录的用户,后续的操作请求访问到B节点的接口,但是B节点session中没有用户身份信息,就会导致重新跳转到登录页的情况。

    此文使用SpringBoot+Spring-session+Redis实现Session的共享

    pom.xml引入依赖

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            
            <!--redis-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
                <version>2.3.0.RELEASE</version>
            </dependency>
    
            <!-- session统一由redis管理 -->
            <dependency>
                <groupId>org.springframework.session</groupId>
                <artifactId>spring-session-data-redis</artifactId>
            </dependency>

    application.yml配置

    spring:
      session:
      	#session存储方式
        store-type: redis
      redis:
        host: 127.0.0.1
        port: 6379
        #redis的超时时间
        timeout: 3000
        #设置会话操作后立即更新到redis中,默认是等服务器请求结束后再将变化的值同步到redis中
        flush-mode: immediate
        pool:
          # 连接池中的最大空闲连接
          min-idle: 0
          # 连接池中的最大空闲连接
          max-idle: 8
          # 连接池最大连接数(使用负值表示没有限制)
          max-active: 8
          # 连接池中的最小空闲连接
          max-wait: -1

    编写session拦截器

    • 1.查看session中的user是否为null
    • 2.根据用户名去redis查询对应的sessionId信息
    • 3.如果查询的为null或者取出来的sessionId和当前会话的sessionId不一致,则拦截,否则放行
    @Component
    public class RedisSessionInterceptor implements HandlerInterceptor {
    
        @Autowired
        private StringRedisTemplate redisTemplate;
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            HttpSession session = request.getSession();
            if (session.getAttribute("user") != null) {
                try {
                    User user = (User) session.getAttribute("user");
                    //验证当前请求的session是否是已登录的session
                    String loginSessionId = redisTemplate.opsForValue().get("loginUser:" + user.getUsername());
                    if (loginSessionId != null && loginSessionId.equals(session.getId())) {
                        return true;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            response401(response);
            return false;
        }
    
        private void response401(HttpServletResponse response) {
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
            try {
                response.getWriter().print("用户未登录或登陆超时!");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    启用RedisHttpSession和注册拦截器

    对除了登录接口的所有以**/api**开头的接口进行拦截.

    @EnableRedisHttpSession(maxInactiveIntervalInSeconds = 20)//session过期时间(秒)
    @Configuration
    public class RedisSessionConfig implements WebMvcConfigurer {
        @Autowired
        RedisSessionInterceptor redisSessionInterceptor;
        @Override
        public void addInterceptors(InterceptorRegistry registry)
        {
            //所有已api开头的访问都要进入RedisSessionInterceptor拦截器进行登录验证;
            registry.addInterceptor(redisSessionInterceptor).addPathPatterns("/api/**").excludePathPatterns("/api/login/**");
        }
    }

    测试

    测试用的Model类

    public class User implements Serializable {
        private String username;
        private String loginSessionId;
    }
    

    存放到session的model必须实现Serializable接口,不然会出现org.springframework.data.redis.serializer.SerializationException序列化异常。

    测试用的接口

    @RestController
    @RequestMapping("/api")
    public class UserController {
    
        @Autowired
        private StringRedisTemplate redisTemplate;
    
        @GetMapping("/login/{username}")
        public User login(HttpSession session, @PathVariable String username){
            User user=new User();
            user.setUsername(username);
            user.setLoginSessionId(session.getId());
            session.removeAttribute("user");
            session.setAttribute("user",user);
            redisTemplate.opsForValue().set("loginUser:"+username, session.getId());
            return user;
        }
    
        @GetMapping("/index")
        public User index(HttpSession session){
            User user=(User) session.getAttribute("user");
            return user;
        }
    }

    测试流程

    1.首先启动应用2次,端口例如8020,8021

    2.打开浏览器访问: http://localhost:8020/api/login/test

     

    3.再打开一个窗口访问: http://localhost:8021/api/index

     可以看到上面2个图片返回的sessionId是一致的,表示着操作已经成功了

    4.测试session拦截器是否有效

    @EnableRedisHttpSession(maxInactiveIntervalInSeconds = 20)

    由于我上面配置的session超时时间是20秒,所以等20秒后再访问/api/index接口就可以看到请求已经被成功拦截了。

  • 相关阅读:
    非系统服务如何随系统启动时自动启动(rc.local加了可执行权限,仍然没有生效)
    centos7安装部署mysql5.7服务器
    centos7配置openldap服务器
    MySQL之高可用MHA部署
    root用户登录mysql后新建用户提示1045错误
    centos7上部署vnc服务器并实现远程桌面
    xenserver开启虚拟机时提示找不到存储介质,强制关闭和重启都没用
    固态硬盘和机械硬盘双硬盘安装win10,提示无法找到系统
    centos7安装的mysql无法启动(mysql daemon failed to start)
    实现在同一界面打开putty终端连接工具
  • 原文地址:https://www.cnblogs.com/h-z-y/p/14621584.html
Copyright © 2011-2022 走看看