在上一篇我们聊到了会话技术的基础原理中session和cookie的使用,基于cookie和session可以实现客户端(浏览器)和服务端的会话存储,从请求的无状态变为一定程度的有状态,在文章最后,通过一个简单的演示,看到这样一种现象,即在分布式环境下,假如客户端第一次携带着JSESSINID访问了A服务器的某个接口,再次访问B服务器相同服务的相同接口时,却发现获取到的JSESSINID值为null
很明显,在分布式环境下,基于单机模式下的session和cookie的值是无法跨进程互通,于是我们想,是否可以通过某种方式将JSESSINID或者说一个客户端与服务端的交互凭证存放在某个存储介质中呢?答案是肯定的
在Java中,提供了多种对于此问题的解决方案,目前使用较多也比较成熟的方案像spring-session,token + redis ,JWT,oauth2等,都可以实现在分布式环境下session共享问题,当然这些方案并不是相互隔离的,可以组合搭配,甚至可以基于某一种做定制化的方案都可以
下面来探讨下小而美的分布式session共享的解决方案的spring-session
spring-session提供了一种扩展存储分布式会话session的解决方案,即通过引入spring-session的相关依赖,可以将会话的session信息存放到指定的存储介质中,可以是mongodb,redis,mysql等,实现自由灵活的存取
下面演示基于redis存储session信息实现分布式会话问题的解决
1、添加基础依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency>
</dependencies>
2、配置yml
server:
port: 8081
spring:
#redis基础配置 ###################################
redis:
port: 6379
host: localhost
database: 1
#session配置 ###################################
session:
store-type: redis
timeout: 3600s
可以看到,spring-session提供了丰富的session存储方式,可以根据自己的情况自由选择
3、编写登录接口和获取登录信息接口
@RestController
public class LoginController {
@GetMapping("/login")
public String login(HttpSession session,HttpServletRequest request,HttpServletResponse response,
String username, String password) {
if(username.equals("admin") && password.equals("123456")){
session.setAttribute("login_info",username);
return "login success:" + username;
}
return "login fail:" + username;
}
@GetMapping("/info")
public String getLoginInfo(HttpSession session,HttpServletRequest request,HttpServletResponse response) {
return "info:" + session.getAttribute("login_info");
}
}
仍然按照之前的测试方式,通过浏览器观察下session的信息,
调用接口:http://localhost:8081/login?username=admin&password=123456
这时候发现,cookie一栏不再有JSESSIONID,而是一个SESSION,说明使用了spring-sesion的方式之后,会话信息的存储方式发生了改变
再通过redis的客户端工具观察下redis库中的session存储情况,发现这里多出来了很多和session相关的信息,比如expires表示会话的过期时间等(具体的过期时间可以从TTL的时间看出来)
这时,再去访问获取会话信息接口:
既然看到session的信息存储到了redis,即在过期时间范围内持久化了,就算重启,也可以访问,即重启后,再次访问上面的接口仍然可以看到 info:admin
重启后,再次访问,依然可以访问到会话的信息,下面再启动一个相同的服务,使用端口进行区分
启动成功后,访问:http://localhost:8082/info,发现也可以成功访问到相同的session信息,这样就简单实现了在分布式环境下会话共享的问题
一个扩展配置
在使用redis存储会话信息时候,默认情况下,保存会话信息的key前缀是以spring开头的,在实际开发过程中,我们希望key带有一定的业务标识,比如redis中存储的会话信息,希望和登录用户的ID有关联,就可以使用下面的这个配置:
session.redis.namespace
即存储在redis中的session的key就可以按照自定义的方式存储了,删除Session信息重启下项目再次测试,可以看到,可以的前置就是自定义的了
而实际在开发过程中,更通用的做法是,将保存在redis中的会话信息的key和登录用户的ID进行关联,即在用户登录成功后,保存到redis中,后续在会话有效的时间范围内,用户再次访问时候,通过网关或者通用拦截器校验用户的会话信息即可,着在spring-session中该如何实现呢?这里留下一个小小的疑问,有兴趣的同学可以深入探究下
阅读量:2014
点赞量:0
收藏量:0