集成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接口就可以看到请求已经被成功拦截了。

posted @ 2021-04-06 14:02  hzy_叶子  阅读(188)  评论(0编辑  收藏  举报