【SpringBoot框架篇】19.集成spring-session利用redis来实现session共享

1.简介

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

解决session几种方式,还有其它的就不全列举出来了

  • session复制:例如Tomcat的广播机制
  • session持久化:把session信息放入sql数据库或者nosql数据库中,例如放入redis中
  • 反向代理hash一致性:四层hash和七层hash都可以做,保证一个用户的请求落在一台web-server上,如nginx配置如下即可解决session不一致的问题.
#七层 hash 
upstream tomcatServer{
ip_hash;
server 192.168.31.139:8080;
server 192.168.31.138:8080; 
} 

ip_hash保证每个访客固定访问一个后端服务器,nginx配置这块可以参考我的博客【Nginx从入门到应用

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

2.引入依赖

        <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>

3.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

4.编写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();
        }
    }
}

5.启用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/**");
    }
}

6.测试

测试用的Model类

public class User implements Serializable {
    private String username;
    private String loginSessionId;
    //省略 get,set 方法 ...
}

存放到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;
    }
}

测试流程
第一步.首先启动应用2次,端口例如8020,8021

idea中同一个项目启动2次操作如下
在这里插入图片描述
勾选Allow Paraller run,让idea支持同一个项目启动多次
在这里插入图片描述
启动完第1个端口的时候,把配置文件的端口修改成8021,然后再次启动即可。

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

在这里插入图片描述

第三步.再打开一个窗口访问: http://localhost:8021/api/index
在这里插入图片描述

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

第四步.测试session拦截器是否有效
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 20)

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

到此该博客的任务已经完成,是不是配置起来很简单呢,感兴趣的小伙伴可以自己试试。

7.项目配套代码

gitee代码地址

创作不易,要是觉得我写的对你有点帮助的话,麻烦在gitee上帮我点下 Star

【SpringBoot框架篇】其它文章如下,后续会继续更新。

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
要排除以`/api/`开头的请求,您可以使用Ant路径匹配模式来配置`spring.session.redis.filter-dispatcher-types`属性。具体来说,您可以配置`spring.session.redis.filter-dispatcher-types`属性,使其不拦截`/api/**`路径下的请求。 例如,以下配置将排除以`/api/`开头的所有请求: ``` spring.session.redis.filter-dispatcher-types=REQUEST,ASYNC spring.session.redis.servlet.filter.enabled=false spring.session.store-type=redis spring.session.redis.flush-mode=on_save spring.session.redis.namespace=spring:session spring.session.redis.cleanup-cron=0 * * * * * spring.session.redis.save-mode=on_set_attribute spring.session.redis.redis-url=redis://localhost:6379 spring.session.redis.redis-password=password spring.session.redis.redis-sentinel-master-id=mymaster spring.session.redis.redis-sentinel-nodes=sentinel://localhost:26379,sentinel://localhost:26380,sentinel://localhost:26381 spring.session.redis.redis-sentinel-password=password spring.session.redis.redis-cluster-nodes=localhost:6379,localhost:6380,localhost:6381 spring.session.redis.redis-cluster-max-redirects=3 spring.session.redis.redis-properties.ssl=true spring.session.redis.redis-properties.ssl-truststore=classpath:redis.truststore spring.session.redis.redis-properties.ssl-truststore-password=redispassword spring.session.redis.redis-properties.ssl-keystore=classpath:redis.keystore spring.session.redis.redis-properties.ssl-keystore-password=redispassword spring.session.redis.redis-properties.useSsl=true spring.session.redis.redis-properties.sslProtocols=TLSv1.2,TLSv1.3 spring.session.redis.redis-properties.sslCipherSuites=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 spring.session.redis.redis-properties.sslProvider=JDK spring.session.redis.redis-properties.sslEnableEndpointIdentification=true spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.session.SessionAutoConfiguration ``` 在以上配置中,我们使用了Ant路径匹配模式配置`spring.session.redis.filter-dispatcher-types`属性,使其不拦截`/api/**`路径下的请求。注意,我们也排除了Spring Boot自动配置的会话管理,因为我们已经使用了Spring Session Redis进行会话管理。 请注意,这只是一个示例配置,您需要根据您的具体需求进行修改。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

皓亮君

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值