HTTP 协议是一种无状态协议
,即每次服务端接收到客户端的请求时,都是一个全新的请求,服务器并不知道客户端的历史请求记录。Cookie 和 Session 的主要目的就是为了弥补 HTTP 的无状态特性。
Cookie
什么是Cookie?
Cookie 是一种服务端产生但存储在客户端用于记录客户端状态的机制。
Cookie工作原理
- 客户端首次请求服务器:如果服务器需要记录该用户状态,就使用 response 向客户端颁发一个 Cookie,客户端会把 Cookie 保存起来;
- 客户端再次请求服务器:客户端会把请求连同该 Cookie 一同提交给服务器,服务器检查该 Cookie,以此来辨认用户状态。
首次请求 | 再次请求 |
---|---|
首次请求
- ①request
GET /reader/ HTTP/1.1
Host: jwt1399.top
- ②response
HTTP/1.1 200 OK
Date: Thu, 12 Jul 2021 07:12:20 GMT
Server: Apache
<Set-Cookie: sid=1342077140226724; path=/; expires=Wed,10-Oct-12 07:12:20 GMT>
Content-Type: text/plain; charset=UTF-8
再次请求
- ③request
GET /image/ HTTP/1.1
Host: jwt1399.top
Cookie: sid=1342077140226724
Cookie的首部字段
首部字段名 | 首部类型 | 说明 |
---|---|---|
Set-Cookie | 响应首部字段 | 开始状态管理所使用的Cookie信息 |
Cookie | 请求首部字段 | 服务器接收到的Cookie信息 |
Set-Cookie属性
属性 | 说明 |
---|---|
name | Cookie的名称。Cookie一旦创建,名称便不可更改 |
value | Cookie的值。如果值为Unicode字符,需要为字符编码。如果为二进制数据,则需要使用base64编码 |
expires | Cookie的有效期(Cookie被删除时的时间戳)。格式为GMT,若设置为以前的时间,则该Cookie立刻被删除,并且该时间戳是服务器时间,不是本地时间!若不明确指定则默认为浏览器关闭前为止。 |
path | Cookie的使用路径。若不指定则默认为文档所在的文件目录 |
domain | 可以访问该Cookie的域名。如果设置为“.google.com”,则所有以“google.com”结尾的域名都可以访问该Cookie。注意第一个字符必须为“.” |
secure | 仅在HTTPS 安全通信时才会发送Cookie,默认为false |
HttpOnly | 加以限制,使Cookie不能被JavaScript 脚本访问 |
maxAge | Cookie失效的时间,单位秒。如果为正数,则该Cookie在maxAge秒后失效。如果为负数,该Cookie为临时Cookie,关闭浏览器即失效,浏览器也不会以任何形式保存该Cookie。如果为0,表示删除该Cookie。默认为-1 |
Size | Cookie的大小。在所有浏览器中,任何Cookie大小超过限制都被忽略,且永远不会被设置。各个浏览器对Cookie的最大值和最大数目有不同的限制 |
SameSite | 用来限制第三方 Cookie,从而减少安全风险。它有3个属性,分别是:Strict,Lax,None |
Priority | 优先级,chrome的提案,定义了三种优先级,Low/Medium/High,当cookie数量超出时,低优先级的cookie会被优先清除。 |
comment | Cookie的用处说明,浏览器显示Cookie信息的时候显示该说明 |
version | Cookie使用的版本号。0表示遵循Netscape的Cookie规范,1表示遵循W3C的RFC 2109规范 |
实例测试
我们可以编写一个测试用例来看看:
Cookie cookie = new Cookie("test", "yyds");
resp.addCookie(cookie);
resp.sendRedirect("time");
for (Cookie cookie : req.getCookies()) {
System.out.println(cookie.getName() + ": " + cookie.getValue());
}
我们可以观察一下,在HttpServletResponse
中添加Cookie之后,浏览器的响应头中会包含一个Set-Cookie
属性,同时,在重定向之后,我们的请求头中,会携带此Cookie作为一个属性,同时,我们可以直接通过HttpServletRequest
来快速获取有哪些Cookie信息。
最关键的其实是name
、value
、maxAge
、domain
属性。
那么我们来尝试修改一下maxAge来看看失效时间:
cookie.setMaxAge(20);
设定为20秒,我们可以直接看到,响应头为我们设定了20秒的过期时间。20秒内访问都会携带此Cookie,而超过20秒,Cookie消失。
既然了解了Cookie的作用,我们就可以通过使用Cookie来实现记住我功能,我们可以将用户名和密码全部保存在Cookie中,如果访问我们的首页时携带了这些Cookie,那么我们就可以直接为用户进行登陆,如果登陆成功则直接跳转到首页,如果登陆失败,则清理浏览器中的Cookie。
那么首先,我们先在前端页面的表单中添加一个勾选框:
<div>
<label>
<input type="checkbox" placeholder="记住我" name="remember-me">
记住我
</label>
</div>
接着,我们在登陆成功时进行判断,如果用户勾选了记住我,那么就讲Cookie存储到本地:
if(map.containsKey("remember-me")){ //若勾选了勾选框,那么会此表单信息
Cookie cookie_username = new Cookie("username", username);
cookie_username.setMaxAge(30);
Cookie cookie_password = new Cookie("password", password);
cookie_password.setMaxAge(30);
resp.addCookie(cookie_username);
resp.addCookie(cookie_password);
}
然后,我们修改一下默认的请求地址,现在一律通过http://localhost:8080/yyds/login
进行登陆,那么我们需要添加GET请求的相关处理:
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Cookie[] cookies = req.getCookies();
if(cookies != null){
String username = null;
String password = null;
for (Cookie cookie : cookies) {
if(cookie.getName().equals("username")) username = cookie.getValue();
if(cookie.getName().equals("password")) password = cookie.getValue();
}
if(username != null && password != null){
//登陆校验
try (SqlSession sqlSession = factory.openSession(true)){
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUser(username, password);
if(user != null){
resp.sendRedirect("time");
return; //直接返回
}
}
}
}
req.getRequestDispatcher("/").forward(req, resp); //正常情况还是转发给默认的Servlet帮我们返回静态页面
}
现在,30秒内都不需要登陆,访问登陆页面后,会直接跳转到time页面。
现在已经离我们理想的页面越来越接近了,但是仍然有一个问题,就是我们的首页,无论是否登陆,所有人都可以访问,那么,如何才可以实现只有登陆之后才能访问呢?这就需要用到Session了。
Cookie缺点
- Cookie 在 HTTP 消息中是明文传输,没有加密
- Cookie 存储于浏览器,可以被篡改和攻击
- Cookie 大小受到限制(通常限制为50 个,每个不超过4KB)
Session
什么是Session?
Session 是一种存储在服务器端用于记录客户端状态的机制。
Session工作原理
- 客户端首次请求服务器:服务器端会生成一个 Session ID 自己先保存下来,再发送给客户端,客户端收到后会将它保存在 Cookie 中
- 客户端再次请求服务器:客户端会把请求连同该 Session ID 一同提交给服务器,服务器检查该 Session ID,以此来辨认用户状态。
session的生命周期
Session何时生效:
Session 在用户访问首次访问服务器时创建,需要注意只有访问JSP、Servlet等程序时才会创建Session,只访问 HTML、IMAG 等静态资源并不会创建 Session,可调用 request.getSession(true) 强制生成 Session
Session何时失效:
服务器会把长时间没有活动的 Session 从服务器内存中清除,此时 Session 便失效。Tomcat 中Session 的默认失效时间为 30 分钟。
实例测试
那么现在,我们在用户登录成功之后,将用户对象添加到Session中,只要是此用户发起的请求,我们都可以从HttpSession
中读取到存储在会话中的数据:
HttpSession session = req.getSession();
session.setAttribute("user", user);
同时,如果用户没有登录就去访问首页,那么我们将发送一个重定向请求,告诉用户,需要先进行登录才可以访问:
HttpSession session = req.getSession();
User user = (User) session.getAttribute("user");
if(user == null) {
resp.sendRedirect("login");
return;
}
在访问的过程中,注意观察Cookie变化。
Session并不是永远都存在的,它有着自己的过期时间,默认时间为30分钟,若超过此时间,Session将丢失,我们可以在配置文件中修改过期时间:
<session-config>
<session-timeout>1</session-timeout>
</session-config>
我们也可以在代码中使用invalidate
方法来使Session立即失效:
session.invalidate();
现在,通过Session,我们就可以更好地控制用户对于资源的访问,只有完成登陆的用户才有资格访问首页。
Session缺点
- 基于 Cookie 的机制容易被 CSRF
- 如果是分布式部署,需要做多机共享 Session 机制
- 存储在服务端,占用资源
两者对比
Cookie 机制是通过检查客户身上的“通行证”来确认客户身份
Session 机制是通过检查服务器上的“客户明细表”来确认客户身份
Cookie | Session | |
---|---|---|
存储位置 | 客户端 | 服务端 |
存储大小 | 有限制 | 无限制 |
跨域支持 | 支持 | 不支持 |
存储信息 | 不加密 | 加密 |
生命周期 | 可自定义 | 不可自定义 |
面试题
如果没有 Cookie 的话 Session 还能用吗?
一般是通过 Cookie
来保存 SessionID
,假如你使用了 Cookie
保存 SessionID
的方案的话, 如果客户端禁用了 Cookie
,那么 Session
就无法正常工作。
但是,并不是没有 Cookie
之后就不能用 Session
了,比如你可以将 SessionID
放在请求的 url
里面https://javaguide.cn/?Session_id=xxx
。这种方案的话可行,但是安全性和用户体验感降低。当然,为了你也可以对 SessionID
进行一次加密之后再传入后端。
参考
Sponsor❤️
您的支持是我不断前进的动力,如果您感觉本文对您有所帮助的话,可以考虑打赏一下本文,用以维持本博客的运营费用,拒绝白嫖,从你我做起!🥰🥰🥰
支付宝 | 微信 |