一:Web容器Session
HTTP是无状态协议的,服务器端要想记住客户端的状态需要通过Session和Cookie来完成。
Session是由Web服务器端(Tomcat)维护的,Cookie是由客户端(浏览器)维护的,浏览器每次请求都会自动携带Cookie信息,当服务器端首次往session中存储值时(session.setAttribute(name, value)
),服务器端(Tomcat)会自动向响应头(Response Head)中增加一个Set-Cookie的头值为JSESSIONID的键值对Set-Cookie: JSESSIONID=B509E856BF4E4F2A0D3C572179A6857F
,当客户端(浏览器)下次访问服务器时浏览器会自动携带Cookie并且该Cookie中会包含JSESSIONID,当服务器收到客户端的请求时服务器就能解析到Cookie中的JSESSIONID并根据JSESSIONID值获取对应的Session对象,这样服务器端就知道客户端的状态了,就知道该请求是哪个用户请求的了。
Session对应着一个浏览器窗口,当浏览器关闭了该Session也就消失了,即关掉浏览器再次打开浏览器访问服务器首次访问session也会重新创建一个新的Session。不同类型的浏览器对Session是隔离的,不能互相访问。不同类型的浏览器首次访问也都会创建不同的session。
Cookie是保存在浏览器中的,即保存在本机的,我们可以直接修改Cookie中的JSESSIONID的值,客户端是否修改JSESSIONID的值服务器是不知道的,服务器只会通过JSESSIONID获取Session,我们可以将JSESSIONID中的值修改为别人的值(可以通过浏览器插件EditThisCookie来修改Cookie的值),这样能够骗过服务器拿到别人session的信息,这样是不安全的。即Cookie是不安全的。
二:Spring Session
传统Session的问题
Session是由Web容器管理的,即一个session只保存在一台机器上,适合于单体应用,随着架构的演练,不断的向微服务分布式集群演进,传统的Session就不能工作了。如:现在有3台web服务器,客户端访问服务器通过负载均衡nginx负载到某一台服务器上,用户此次的数据就保存到这台服务器的Web容器中了,当用户下次请求如果被负载到其它机器上,那么就拿不到之前保存的数据了。这时候就需要整个服务器集群共享同一个Session。为了解决所有服务器共享一套Session,那么Session就不能单独保存在自己的Web容器中,而是保存在一个公共的会话仓库(Session Repository)中,所有服务器都访问同一个仓库,这样所有服务器的状态都一致了。Spring Session支持的仓库有Reids、MongoDB、JDBC。
Spring Session的优点
- Spring Session是基于servlet规范实现的一套Session管理框架。Spring Session主要解决了分布式场景下Session的共享问题。Spring Session最核心的类是SessionRepositoryFilter过滤器,用于包装用户的请求和响应。
- 可在程序中直接替换掉HttpSession,而无需修改一行代码
- 可以很方便的与Spring Security集成,增加诸如findSessionsByUserName,rememberMe,限制同一个账号可以同时在线的Session数(如设置成1,即可达到把前一次登录顶掉的效果)等等
Spring Session的核心类
- org.springframework.session.web.http.SessionRepositoryFilter: 会话仓库过滤器
- SessionRepositoryRequestWrapper extends HttpServletRequestWrapper
- getSession(boolean create)
- SessionRepositoryResponseWrapper extends HttpServletResponseWrapper
- HttpSessionWrapper
- org.springframework.session.SessionRepository
- createSession()
- save(S session)
- deleteById(String id)
三:Spring Session示例
- pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
- application.yml
spring:
redis:
host: localhost
port: 6379
database: 0
jedis:
pool:
max-active: 100
max-wait: 10
max-idle: 10
min-idle: 10
- Configuration
/**
* 开启Redis Http Session
*/
@Configuration
@EnableRedisHttpSession
public class RedisHttpSessionConfiguration {
}
- Controller
@RestController
public class SessionController {
@RequestMapping("/session")
public Object springSession(@RequestParam("username") String username, HttpServletRequest request, HttpSession session) {
Cookie[] cookies = request.getCookies();
if (cookies != null && cookies.length > 0) {
for (Cookie cookie : cookies) {
if (cookie.getName().contains("JSESSION")) {
System.out.println(cookie.getName() + "=" + cookie.getValue());
}
}
}
Object value = session.getAttribute("username");
if (value == null) {
System.out.println("用户不存在");
session.setAttribute("username", "{username: '" + username+ "', age: 28}");
} else {
System.out.println("用户存在");
}
return "username=" + value;
}
}
- 启动项目
# 将springboot项目打成jar包
cd springboot-session-example
mvn clean package
# 分别以两个不同的端口启动两个项目, 用于模拟集群中的两个服务
cd springboot-session-example/target
java -jar springboot-session-example-0.0.1-SNAPSHOT.jar --server.port=8080
java -jar springboot-session-example-0.0.1-SNAPSHOT.jar --server.port=8081
- 第一次访问8080项目,服务器响应头中会有
Set-Cookie: SESSION=MDIzNTk4NTgtODllOC00NjhiLTljNDYtZDFjMmM3OTljNzhk; Path=/; HttpOnly; SameSite=Lax
, 客户端就会将SESSION写入到Cookie中, 注意Spring Session中的键为SESSION,这里的Path=/ 表示根站点 - 查看redis可以看到生成3个key,其中最重要的key是
spring:session:sessions:xxx
记录这session的信息 - 第二次访问8080项目,浏览器会在请求头中自动携带Cookie信息,并且Cookie中会包含SESSION对应的键值对,服务器端根据Cookie中的SESSION就能获取到对应的Session对象
- 在同一个浏览的另一个tab页来访问8081,可以看到请求头中的Cookie对应的SESSION值和访问8080工程携带的SESSION值一样
四:参考文章
- 程序员DD http://blog.didispace.com/tags/Spring-Session/
- spring-session揭秘 https://www.cnblogs.com/lxyit/default.html?page=4