概述
session会话管理
- 大多数的应用都是使用cookie来进行session会话跟踪的 即每次发送请求时客户端都会发送一个含有sessionid的cookie到服务端中 这样服务端就会知道这个客户端是谁了
- 若客户端禁用了cookie 则一般的会使用url重写以代替cookie来进行session会话跟踪 即每次发送请求时url都会带上一个诸如sessionid这样的参数
- 问题
- 一般的 session是用来存储/记录用户的状态/相关的数据 且一般是交由诸如tomcat这样的容器管理 但若项目中存在多个tomcat 则这多个tomcat之间无法共享session 且一旦tomcat关闭/重启也会导致session的消失
- 解决
- 使用插件 如基于tomcat的tomcat-redis-session-manager(将session存到redis中) 但由于过度依赖容器 即tomcat一旦升级/更换成另一容器如jetty则得重新配置
- 使用nginx的负载均衡策略中的ip_hash(将请求的ip进行hash 再根据hash值选择tomcat) 但缺点是ip不能变 即若发送请求的手机跨省了则ip会改变 也就会定位到另一台tomcat中去 且若此tomcat宕掉则也是不能完成session共享的
- 使用框架 如springsession 既不依赖容器又不需要改动代码 session存储到redis中
SpringSession
- 提供api与实现以管理session
- 提供httpsession以替代容器中的session
- 支持cluster中的session处理
同域名相同项目(非集群)下的session共享
添加依赖
<dependencies>
<!-- springboot集成redis依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- springsession依赖 -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
创建controller
@RestController
public class MyController {
@RequestMapping("/set")
public Object set(HttpSession session) {
session.setAttribute("data", "my session data!");
return "set session ok!";
}
@RequestMapping("/get")
public Object get(HttpSession session) {
return session.getAttribute("data");
}
}
配置application.yml
spring:
redis:
host: 192.168.190.128
port: 6379
server:
port: 8081
servlet:
session:
#设置springsession中session的生命周期为30m(默认值)
timeout: 30m
启动redis以进行测试
- 使用resp时记得关闭防火墙、注释掉bind、关闭protected-mode以让外部ip可以直接访问到此redis
- 要启动两个端口不同的tomcat 8081与8082
- 快速复制:复制一份8081并在"Program arguments"处写上"–server.port=8082"即可
同域名相同项目(集群)下的session共享
-
即tomcat集群与nginx负载均衡下的session共享
-
与第一种类似
-
先复制tomcat成两份 再将项目打成两个端口不同但包名相同的war包且分别放到两个tomcat的webapps目录下
spring: redis: host: 192.168.190.128 port: 6379 server: port: 8081 servlet: session: #设置springsession中session的生命周期为30m(默认值) timeout: 30m
@RestController public class MyController { @RequestMapping("/set") public Object set(HttpSession session) { session.setAttribute("data", "my session data!"); //方便看到效果 return "set session ok!-8081"; } @RequestMapping("/get") public Object get(HttpSession session) { //方便看到效果 return session.getAttribute("data") + "-8081"; } }
<build> <!-- 指定打包后的文件名称 --> <finalName>boot</finalName> .. </build>
-
配置nginx
upstream www.boot.com { server 127.0.0.1:8081; server 127.0.0.1:8082; } location /boot { proxy_pass http://www.boot.com; }
-
修改hosts文件
192.168.190.128 www.boot.com
-
启动tomcat、nginx、redis
-
访问www.boot.com/boot/get或www.boot.com/boot/set
同域名不同项目下的session共享
-
如www.web.com/user与www.web.com/shop 服务器认为这是两个不同的session 因为路径不同
-
与第一种类似
-
8081中设置context-path与cookie.path 8082中在"Program arguments:“处写上”–server.port=8082 --server.servlet.context-path=/shop"即可
spring: redis: host: 192.168.190.128 port: 6379 server: port: 8081 servlet: session: #设置springsession中session的生命周期为30m(默认值) timeout: 30m cookie: #设置cookie的路径为根路径以实现同域名不同项目下的session共享 path: / context-path: /user
同根域名不同二级子域名的项目下的session共享
-
如www.web.com与beijing.web.com与hainan.web.com 服务器认为这是三个不同的session 因为域名不同
-
与第三种类似
-
8081与8082中都设置cookie.domain并修改hosts文件即可
spring: redis: host: 192.168.190.128 port: 6379 server: port: 8081 servlet: session: #设置springsession中session的生命周期为30m(默认值) timeout: 30m cookie: #设置cookie的路径为根路径以实现同域名不同项目下的session共享 path: / #设置cookie的域名为根域名以实现同根域名不同二级子域名的项目下的session共享 domain: web.com context-path: /user
127.0.0.1 www.web.com 127.0.0.1 beijing.web.com 127.0.0.1 hainan.web.com
单点登录
- 单点登录(SSO/Single Sign-On)指在多个应用系统中 用户只需要登录一次就可以访问所有与其相互信任的其他的应用系统
- 即不同根域名下的session共享 即一处登录、处处登录 如登录了淘宝www.taobao.com则天猫www.tmall.com也是已登录的状态
- springsession不支持单点登录功能
原理分析
- 当发送请求时 SessionRepositoryFilter会先拦截到此请求并使用此过滤器中的doFilterInternal()将原生的HttpServletRequest与HttpServletResponse包装成SessionRepositoryRequestWrapper与SessionRepositoryResponseWrapper 之后获取session将不再通过原生的request.getsession()而是使用wrappedRequest.getsession() 且此方法是被override过的 原理为先从当前请求的attribute中获取session 若没有则去查找一个key为session的cookie 通过此cookie获取到sessionid 再在redis中查找此sessionid