目录
1. 会话管理概述
1.1 为什么需要会话管理
状态无法持久化
在Web应用中,HTTP协议是一种无状态协议,服务器对每个请求都是独立处理的,无法直接将用户的状态信息保存在服务器端。这就导致了无法简单地实现用户在多个页面之间的状态共享和持久化。
多用户并发访问问题
Web应用通常需要同时服务多个用户,每个用户的请求都需要被正确地路由和处理。会话管理能够帮助区分和管理不同用户的请求,确保它们的状态不会相互干扰。
实现用户体验的必要性
良好的用户体验是现代Web应用的重要组成部分。会话管理可以帮助实现以下方面的用户体验:
个性化设置:通过会话管理,用户可以在不同的页面之间保存个性化的设置(例如语言偏好、主题选择等),从而提供更符合用户需求的体验。
购物车功能:对于电子商务网站来说,会话管理能够帮助用户在浏览商品的过程中将商品加入购物车,并在整个会话期间保持购物车的状态,以便用户最终完成购买操作。
1.2 会话管理的实现方式
Cookie
Cookie 是最常见的会话管理机制之一,它是服务器发送给客户端的小型文本文件,存储在客户端的浏览器中。通过设置 Cookie 中的标识符和数值,可以在客户端和服务器之间传递状态信息。
URL重写
URL 重写是一种在 URL 中添加会话标识符的方法,通过在 URL 的查询参数中包含会话标识符,来实现会话跟踪和管理。
Session
Session 是在服务器端维护的一个对象,它存储了与特定用户相关的信息。通过在客户端使用 Cookie 或者 URL 重写传递会话标识符,服务器能够识别并关联对应的 Session 对象。
1.3 会话管理的安全性考虑
在设计和实现会话管理时,需要考虑以下安全性问题:
会话劫持:攻击者通过窃取会话标识符,冒充合法用户的身份进行恶意操作。为了防范会话劫持,可以使用安全的传输协议(如HTTPS)、设置合适的 Cookie 属性(如Secure
和 HttpOnly
),以及定时更新会话标识符。
会话固定攻击:攻击者试图将自己的会话标识符注入到合法用户的会话中,以获取其权限。为了防范会话固定攻击,应该避免在 URL 中暴露会话标识符,以及使用随机生成的会话标识符。
会话过期管理:及时使过期的会话失效,防止已经离开或者关闭浏览器的用户的会话被滥用。
2. Cookie
2.1 Cookie概述
什么是Cookie
Cookie是一种由服务器发送到客户端并存储在客户端上的小型文本文件,用于保存用户的状态信息和偏好设置。每当用户访问同一服务器时,浏览器会自动发送这些Cookie,从而使服务器能够识别用户并提供个性化服务。
Cookie的工作原理
服务器发送Cookie:当用户首次访问某个网站时,服务器会通过HTTP响应头中的Set-Cookie字段发送一个或多个Cookie到客户端。
浏览器存储Cookie:客户端浏览器接收到Cookie后,会将其存储在本地(通常是文本文件中)。
浏览器发送Cookie:在随后对同一域名的请求中,浏览器会在HTTP请求头中包含之前存储的Cookie,以便服务器识别用户身份和状态。
服务器处理Cookie:服务器接收到包含Cookie的请求后,可以读取Cookie中的信息,并基于此信息进行相应的处理,如用户认证、会话管理等。
2.2 Cookie的使用
设置Cookie
服务器可以通过设置HTTP响应头中的Set-Cookie
字段来设置Cookie。客户端(浏览器)也可以通过JavaScript来设置Cookie。
例如,通过HTTP响应头设置Cookie:
Set-Cookie: name=value; Expires=Wed, 21 Oct 2024 07:28:00 GMT; Path=/; Domain=example.com; Secure; HttpOnly
通过JavaScript设置Cookie:
document.cookie = "name=value; expires=Wed, 21 Oct 2024 07:28:00 GMT; path=/; domain=example.com; secure; httponly";
基本属性
- 名称(Name):Cookie的名称,用于标识该Cookie。
- 值(Value):Cookie的值,存储实际需要保存的信息。
- 域(Domain):指定Cookie所属的域名,只有在这个域名下的请求中,Cookie才会被发送。默认为当前文档的域。
- 路径(Path):指定Cookie的有效路径。只有在这个路径及其子路径下的请求中,Cookie才会被发送。默认为当前文档的路径。
获取Cookie
可以通过JavaScript的document.cookie
属性获取当前域名下的所有Cookie。返回的字符串形式是一个由分号和空格分隔的键值对列表。
例如:
let allCookies = document.cookie;
console.log(allCookies);
读取、解析 Cookie信息
为了方便使用,可以编写函数来解析Cookie字符串,将其转换为对象形式。
function getCookie(name) {
let cookieArr = document.cookie.split("; ");
for (let i = 0; i < cookieArr.length; i++) {
let cookiePair = cookieArr[i].split("=");
if (name == cookiePair[0]) {
return decodeURIComponent(cookiePair[1]);
}
}
return null;
}
2.3 Cookie的时效性
Cookie的过期机制
Cookie可以通过Expires
或Max-Age
属性来设置其过期时间。如果未设置过期时间,则默认是会话Cookie,在浏览器关闭时删除。
- Expires:设置具体的过期日期和时间。
- Max-Age:设置从现在开始计算的存活时间(以秒为单位)。
例如:
Set-Cookie: name=value; Expires=Wed, 21 Oct 2024 07:28:00 GMT;
Set-Cookie: name=value; Max-Age=3600;
持久Cookie与会话Cookie
- 持久Cookie:具有具体的过期时间,存储在客户端直到过期。
- 会话Cookie:没有设置过期时间,随着浏览器关闭而删除。
Cookie的安全性问题
为了增强Cookie的安全性,可以使用以下属性:
- HttpOnly:防止JavaScript访问Cookie,减少XSS攻击风险。
- Secure:确保Cookie仅通过HTTPS传输,防止在HTTP传输过程中被窃取。
例如:
Set-Cookie: name=value; Secure; HttpOnly;
2.4 Cookie的提交路径
Cookie作用范围
Cookie的作用范围由其Domain
和Path
属性决定。只有满足这两个条件的请求才会携带相应的Cookie。
同一域名下的路径限制
通过Path
属性可以限定Cookie只在特定路径及其子路径下有效。例如:
Set-Cookie: name=value; Path=/subpath;
上述Cookie只会在/subpath
及其子路径下发送。
跨域问题
默认情况下,Cookie不能跨域传递。即使两个域名属于同一个顶级域(如example.com
和sub.example.com
),它们之间的Cookie也不能共享。可以通过设置Domain
属性来实现子域之间的共享,但不能跨不同的顶级域。
例如,设置Cookie在整个example.com
域内共享
Set-Cookie: name=value; Domain=example.com;
Cookie的限制与解决方案
- 大小限制:单个Cookie的大小通常限制在4KB以内,每个域名下的Cookie总数也有一定限制(各浏览器有所不同)。
- 数量限制:单个域名下最多可存储的Cookie数量有限制(通常为20-50个)。
- 解决方案:对于超出限制的数据,可以考虑使用其他存储机制(如LocalStorage、SessionStorage)来存储较大的数据,或者将数据分片存储在多个Cookie中。
3. Session
3.1 HttpSession概述
什么是Session
HttpSession是一种保留更多信息在服务端的一种技术,服务器会为每一个客户端开辟一块内存空间,即session对象. 客户端在发送请求时,都可以使用自己的session. 这样服务端就可以通过session来记录某个客户端的状态了
-
服务端在为客户端创建session时,会同时将session对象的id,即JSESSIONID以cookie的形式放入响应对象
-
后端创建完session后,客户端会收到一个特殊的cookie,叫做JSESSIONID
-
客户端下一次请求时携带JSESSIONID,后端收到后,根据JSESSIONID找到对应的session对象
-
通过该机制,服务端通过session就可以存储一些专门针对某个客户端的信息了
-
session也是域对象
session的特点
- 持久性:Session是一种持久性连接,可以在多个请求之间保持状态信息。
- 安全性:Session数据存储在服务器端,相对于Cookie等客户端存储的方式更加安全。
- 个性化:每个用户的Session都是独立的,可以存储用户特定的个性化信息。
- 一致性:通过Session可以实现用户请求的有序处理,保证数据的一致性。
3.2 HttpSession的使用
创建与管理Session
在Java中,可以使用HttpSession
对象来创建和管理Session。一般情况下,当用户首次访问服务器时,服务器会自动创建一个新的Session,并为其生成一个唯一的标识符(Session ID)。
例如,在Servlet中创建Session:
HttpSession session = request.getSession();
Session的生命周期
Session的生命周期包括创建、激活、钝化、钝化传输、激活传输和销毁等阶段。其中,创建和销毁由容器负责管理,而激活和钝化是为了在Session长时间不活动时将其存储到磁盘,避免占用过多内存。
存储与获取Session数据
可以通过setAttribute
和getAttribute
方法来存储和获取Session中的数据:
// 存储数据
session.setAttribute("username", "John");
// 获取数据
String username = (String) session.getAttribute("username");
3.3 HttpSession的时效性
为什么要设置session的时效
-
用户量很大之后,Session对象相应的也要创建很多。如果一味创建不释放,那么服务器端的内存迟早要被耗尽。
-
客户端关闭行为无法被服务端直接侦测,或者客户端较长时间不操作也经常出现,类似这些的情况,就需要对session的时限进行设置了
Session的超时管理
Session有一个超时时间,超过这个时间没有活动则会被销毁。超时时间可以在服务器配置中指定,默认是30分钟。
配置Session超时时间
在web.xml或者Servlet中可以通过以下方式配置Session的超时时间:
<session-config>
<session-timeout>30</session-timeout> <!-- 单位为分钟 -->
</session-config>
Session的持久化
Session的持久化指的是将Session中的数据保存到持久化存储(如数据库、文件系统)中,以便在服务器重启或集群环境下进行数据共享。
在Java中,可以通过实现HttpSessionActivationListener
接口和HttpSessionBindingListener
接口来监听Session的钝化和激活事件,从而实现Session的持久化。
集群环境下的Session共享问题
在集群环境下,Session共享是一个比较复杂的问题。一般来说,可以通过以下方式实现Session的共享:
- 使用粘滞会话(Sticky Session):在负载均衡器上配置,确保同一个用户的请求都发送到同一个服务器上,从而保证Session的一致性。
- 使用分布式缓存:将Session数据存储在分布式缓存中(如Redis),各个服务器可以共享这些数据,从而实现Session的共享。
4. 三大域对象
4.1 域对象概述
域对象: 一些用于存储数据和传递数据的对象,传递数据不同的范围,我们称之为不同的域,不同的域对象代表不同的域,共享数据的范围也不同
-
web项目中,我们一定要熟练使用的域对象分别是 请求域,会话域,应用域
-
请求域对象是HttpServletRequest ,传递数据的范围是一次请求之内及请求转发
-
会话域对象是HttpSession,传递数据的范围是一次会话之内,可以跨多个请求
-
应用域对象是ServletContext,传递数据的范围是本应用之内,可以跨多个会话
4.2 域对象的使用
域对象的API
API | 功能 |
---|---|
void setAttribute(String name,String value) | 向域对象中添加/修改数据 |
Object getAttribute(String name); | 从域对象中获取数据 |
removeAttribute(String name); | 移除域对象中的数据 |
API测试
-
ServletA向三大域中放入数据
@WebServlet("/servletA")
public class ServletA extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 向请求域中放入数据
req.setAttribute("request","request-message");
//req.getRequestDispatcher("servletB").forward(req,resp);
// 向会话域中放入数据
HttpSession session = req.getSession();
session.setAttribute("session","session-message");
// 向应用域中放入数据
ServletContext application = getServletContext();
application.setAttribute("application","application-message");
}
}
- ServletB从三大于中取出数据
@WebServlet("/servletB")
public class ServletB extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 从请求域中获取数据
String reqMessage =(String)req.getAttribute("request");
System.out.println(reqMessage);
// 从会话域中获取数据
HttpSession session = req.getSession();
String sessionMessage =(String)session.getAttribute("session");
System.out.println(sessionMessage);
// 从应用域中获取数据
ServletContext application = getServletContext();
String applicationMessage =(String)application.getAttribute("application");
System.out.println(applicationMessage);
}
}
-
请求转发时,请求域可以传递数据
请求域内一般放本次请求业务有关的数据,如:查询到的所有的部门信息
-
同一个会话内,不用请求转发,会话域可以传递数据
会话域内一般放本次会话的客户端有关的数据,如:当前客户端登录的用户
-
同一个APP内,不同的客户端,应用域可以传递数据
应用域内一般放本程序应用有关的数据 如:Spring框架的IOC容器