为什么需要session
http协议是一个无状态协议,某些场景我需要session和cookie去记录一些数据。
Session生成时间
第一次使用时,第一次获取如果Session不存在,会生成1个新的Session
第一次访问JSP,访问JSP时会注入九大域对象,其中包括Session
cookie与session
cookie是什么
存储在用户主机浏览器中的一小段文本文件
有服务器生成,发送给浏览器,KV形式存储
session是什么
字面理解为“会话”
保存在服务端的数据结构
protected Map<String, Session> sessions = new ConcurrentHashMap<>();
//...
public Session findSession(String id) throws IOException {
if (id == null) {
return null;
}
return sessions.get(id);
}
//...
服务器通过请求中的Cookies识别回话
Tomcat Request
protected Session doGetSession(boolean create) {
// There cannot be a session if no context has been assigned yet
Context context = getContext();
if (context == null) {
return null;
}
// Return the current session if it exists and is valid
if ((session != null) && !session.isValid()) {
session = null;
}
if (session != null) {
return session;
}
// Return the requested session if it exists and is valid
Manager manager = context.getManager();
if (manager == null) {
return null; // Sessions are not supported
}
if (requestedSessionId != null) {
try {
session = manager.findSession(requestedSessionId);
} catch (IOException e) {
session = null;
}
if ((session != null) && !session.isValid()) {
session = null;
}
if (session != null) {
session.access();
return session;
}
}
//....
}
Tomcat CoyoteAdapter
/**
* Parse session id in Cookie.
*
* @param request The Servlet request object
*/
protected void parseSessionCookiesId(Request request) {
// If session tracking via cookies has been disabled for the current
// context, don't go looking for a session ID in a cookie as a cookie
// from a parent context with a session ID may be present which would
// overwrite the valid session ID encoded in the URL
Context context = request.getMappingData().context;
if (context != null && !context.getServletContext()
.getEffectiveSessionTrackingModes().contains(
SessionTrackingMode.COOKIE)) {
return;
}
// Parse session id from cookies
ServerCookies serverCookies = request.getServerCookies();
int count = serverCookies.getCookieCount();
if (count <= 0) {
return;
}
String sessionCookieName = SessionConfig.getSessionCookieName(context);
for (int i = 0; i < count; i++) {
ServerCookie scookie = serverCookies.getCookie(i);
if (scookie.getName().equals(sessionCookieName)) {
// Override anything requested in the URL
if (!request.isRequestedSessionIdFromCookie()) {
// Accept only the first session id cookie
convertMB(scookie.getValue());
request.setRequestedSessionId
(scookie.getValue().toString());
request.setRequestedSessionCookie(true);
request.setRequestedSessionURL(false);
if (log.isDebugEnabled()) {
log.debug(" Requested cookie session id is " +
request.getRequestedSessionId());
}
} else {
if (!request.isRequestedSessionIdValid()) {
// Replace the session id until one is valid
convertMB(scookie.getValue());
request.setRequestedSessionId
(scookie.getValue().toString());
}
}
}
}
}
单体Session生命周期
单体Session生命周期- 创建:第1次使用request.getSession() 会创建新的会话(并且将id写入到浏览器Cookies中)
- 更新:每次请求都会携带id获取相同的Session,并且更新其最后的访问时间
- 销毁:容器启动时会开启线程时(默认10s)检测Session是否过期,如果过期将会销毁
cookie与session的区别
数据 | 存储位置 | 大小限制 | 是否安全 |
---|---|---|---|
cookie | 用户浏览器 | 50个/4K | 否 (httponly/https) |
session | 服务器 (内存、文件、缓存) |
取决于存储介质 | 是 |
@RestController
public class LoginController {
static Map<String, String> passwordMap = new HashMap<>();
static {
passwordMap.put("zhangsan", "123");
passwordMap.put("lisi", "321");
}
@RequestMapping("/index")
@ResponseBody
public String index(HttpServletRequest request) {
HttpSession httpSession = request.getSession();
Boolean isLogin = (Boolean) httpSession.getAttribute("isLogin");
if (isLogin != null && isLogin) {
return "欢迎 " + httpSession.getAttribute("username") + " 进入首页";
} else {
return "请登录!";
}
}
@RequestMapping("/login")
@ResponseBody
public String login(HttpServletRequest request,
@RequestParam("username") String username,
@RequestParam("password") String password) {
//1、验证用户名和密码
if (!passwordMap.containsKey(username)) {
return "请先注册";
}
if (!passwordMap.get(username).equals(password)) {
return "密码错误";
}
//2.生成session
HttpSession httpSession = request.getSession();
//3.将登陆信息写入session
httpSession.setAttribute("isLogin", Boolean.TRUE);
httpSession.setAttribute("username", username);
//4.返回
return "welcome " + username;
}
}
http://localhost:8081/login?username=lisi&password=321
分布式session
分布式环境session失效问题
session粘粘
session复制
session集中式存储
redis实现分布式session
1.session 粘粘(固定分配)
session 粘粘优点:实现简单
缺点:可扩展性差、分配不均衡
2.session 复制(会话同步)
session 复制缺点:
每台机器存储全量数据,比较占机器内存
session同步当机器较多时,可能造成广播风暴
session同步占用一定的网络带宽
3.session 集中式存储(会话共享)
session 集中式存储 <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>
<version>2.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>
org.springframework.security
</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
</dependency>
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</dependency>
本质上利用Tomcat的Filter的实现类SpringSessionRepositoryFilter实现了对每一次请求的拦截,拦截之后把Session放到Redis里面
允许跨域访问
/**
* 因为服务端返回给客户端的set-cookie中带有samesite=lax,
* 这就是问题的根源,它表示不能携带cookie进行跨域post访问,
* 然而我们是需要携带cookie的
*
* @return
*/
@Bean
public CookieSerializer httpSessionIdResolver() {
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
cookieSerializer.setCookieName("SESSION");
cookieSerializer.setUseHttpOnlyCookie(false);
cookieSerializer.setSameSite(null);
return cookieSerializer;
}
网友评论