哥几个来学 Cookie 和 Session 啦~~
目录
🌲 一、Cookie
Cookie 直接翻译称为:曲奇饼。但是在网络上可与这种 小甜饼干 没有半毛钱关系~~
Cookie 中存储了一个字符串,这个数据可能是客户端(网页)自行通过 JS 写入的,也可能来自于服务器( 服务器在 HTTP 响应的 header 中通过 Set-Cookie 字段给浏览器返回数据 )
Cookie 的值也是由键值对组成的:
键与值之间用“=”分隔;键值对之间用“;”分隔。
这里的键值对是由程序员规定的,不同的网站有不同的 键值对,也就有不同的含义和用途。
Cookie 的本质:
是浏览器在本地存储 用户自定义数据 的一种关键机制。
就拿最典型的 登录操作 举例:
假设我们要登陆 B站,我们输入 账号 和 密码 之后点击登录
登陆成功:
当我们关掉 这个 B站 的页面之后
然后再次 打开我们心爱的 B站
我们发现:并不需要重新登录,我们的浏览器已经帮我们“登录”好了。
实现这种效果离不开我们的 Cookie~~
从上述流程我们不难了解:
Cookie 从哪来?
Cookie 从服务器来。当我们的浏览器访问服务器的时候,服务器就会在 HTTP 响应中,通过 Set-Cookie 字段,把 Cookie 的键值对返回给浏览器。
Cookie 到哪去?
浏览器会在下一次请求的时候,把 Cookie 带给服务器,Cookie 在浏览器这边,只能算是 “暂存” ,真正要让这个数据发挥作用,还得是由服务器来使用。
Cookie 有什么用?
Cookie 是浏览器本地存储数据的机制。存的不一定是账户信息,想存任何东西都可以(前提是得是字符串)。由于 Cookie 的存储空间有限,一般不会用 Cookie 存储太大的数据~~
上面我们提到 Cookie 是浏览器本地存储数据的机制。既然涉及到存储,那么具体怎么个存法呢?
是不是直接存储到硬盘文件上就行了呢?
万万使不得!!
这个操作无异于允许网页直接操作你电脑的文件系统,那么如果你一旦不小心点到了一个奇奇怪怪的网站,那么你的电脑可能就会被这些恶意网站的开发者给 “夺舍了”。
因此,为了保证用户上网能够比较安全,浏览器会作出限制,禁止网页能够直接访问硬盘。
虽然浏览器禁止了直接访问硬盘,但是允许网页往浏览器这里存储一些 Cookie ,这些数据通过浏览器提供的 api,写入特定的文件中。
由于网页由很多很多个,因此每个网站都是存自己的 Cookie (按照域名为维度来进行存储)。
比如同一个网站(B站的舞蹈区、影视区、动画区等)共享一份 Cookie 。而不同的网站(百度、搜狗、B站等)是各有各的 Cookie 。
除了 浏览器禁止网页能够直接访问硬盘 之外,还有一种机制能够防止 Cookie 信息泄漏,那就是
Session (会话) 。
🌳二、Session (会话)
首先引入概念,什么是Session (会话)?
Session (会话)就是服务器这边用于区分用户身份的一种机制,通常是配合cookie来使用的。
会话的本质就是一个哈希表,存储了一些键值对结构,key就是令牌的ID(Session Id),value就是用户的信息,这个用户信息可以可以根据需求灵活设计。
也就是说,Session (会话) 可以通过 Session Id 从 哈希表 中找到用户信息,从而来判断这个用户是谁~~
Session (会话)的开始时间:
浏览器访问服务器的时候就是会话的开始。
Session (会话)的结束时间:
会话的关闭时间比较模糊,当你关掉浏览器的时候并不代表会话结束,不同的网站对于每个用户的会话都设定了确定的 结束会话的时间 和 唯一的 Id 。
这里的 “唯一的 Id” 通常指的是 Session Id:
这个 Session Id 是 服务器 产生的东西,所以一般都会保存在 数据库 中。
注意:这个 Session Id 也可以叫 token(同一事物的不同叫法)
那么问题来了,为什么有了用户名还需要使用 Session Id ?
我们再来演示一下 B站 的登录流程,这次加上 Session(会话)的机制:
由于在 第④步 的时候,Cookie 没有保存 账号密码,而是保存了 在 第③步 已经加到 Cookie 里的 Session Id。而这个 Session Id 是一个 无规律的 字符串,并且服务器在发送这个 Cookie 之前,会对这个含有 Session Id 的 Cookie 进行“签名”。也就是说,如果黑客修改了这个 Session Id ,那么服务器就无法识别这个 Session Id,因此就可以保证信息传输的安全性。
🌴三、Cookie 和 Session 的区别
🍕1. Cookie 可以存储在浏览器或本地,Session 只能存储在服务器。
🍔2. Session 能够存储任意的 Java 对象,Cookie 只能存储 String 类型的对象。
🍟3. Session 比 Cookie 更具有安全性(Cookie 有安全隐患,通过拦截或本地文件可以找到用户的 Cookie ,然后进行攻击)
🌭4. Session 占用服务器性能,如果 Session 过多,会增加服务器压力。
🍿5. 单个 Cookie 有大小限制;而 Session 是没有大小限制的 ,它的它所存储的数据量和服务器大小有关。
🥓6. Cookie 可以单独使用,也可以搭配 Session 使用。
🌵四、实现一个简单的登录功能
🥗核心方法:
HttpServletRequest 类中的相关方法:
方法 | 描述 |
HttpSession getSession() | 在服务器中获取会话.参数如果为 true,则当不存在会话时新建会话;参数如果 为 false,则当不存在会话时返回 null。 |
Cookie[] getCookies() | 返回一个数组,包含客户端发送该请求的所有的 Cookie 对象。会自动把 Cookie 中的格式解析成键值对。 |
实际开发中,使用 getCookies() 的场景非常少,主要还是getSession()方法。
我们主要来看看 getSession 方法:
这个方法在使用时需要传入一个boolean值:
当我们传入的时true时,首先会读取请求中Cookie里面的 Session Id ,在服务器这边根据 Session Id 来查询对应的 Session 对象。
如果查询到了,就直接返回这个 Session 对象。
如果没有查询到,就会才创建一个 Session 对象,同时生成一个 Session Id , 以 Session Id 为key,以 Session 对象为value,把这个键值对存储在服务器里面的一个hash表中 同时把 Session Id 以 set-Cookie 的方法返回给浏览器。
当我们传入的是false时,读取请求中 Cookie 里面的 Session Id ,在服务器这边根据 Session Id 来查询对应的 Session 对象 。
如果查询到了,就直接返回这个 Session 对象。
如果没有查询到,就返回一个null。
现在我们通过实现用户登录的例子来综合的理解Cookie和Session:
提交的数据格式为form表单的形式 Content-Type:application/x-www-form-urlencoded
例如username=zhangsan&password=123
一般向这样的登录请求,都是使用post方法比较好。属于是使用习惯。
首先要实现一个用户登录的场景,我们先要得到前端的页面。
🥙前端页面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录页面</title>
</head>
<body>
<form action="login" method="post">
<input type="text" name="username">
<input type="password" name="password">
<input type="submit" value="login">
</form>
</body>
</html>
可以看到上述代码中,我们是以form表单的形式提交的数据,方法是post,路径为login。
页面效果:
当我们点击登录的时候,就会把我们输入框里面的用户名和密码以form表单的形式通过post方法向路径为login提交数据。
如果用户名和密码正确,我们就跳转到一个新的页面,如果不正确,我们就提交用户名或密码错误。
🥪后端代码:
后端我们分了两个 Servlet 来实现,一个 Servlet 用来实现账号密码验证功能,一个用来生成主页。
🍧账号密码校验的Servlet:
package login;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
// 这个类用来实现登录时的校验.
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 先从请求中拿到用户名和密码.
// 为了保证读出来的参数也能支持中文, 要记得设置请求的编码方式是 utf8
req.setCharacterEncoding("utf8");
String username = req.getParameter("username");
String password = req.getParameter("password");
// 2. 验证用户名密码是否正确
if (username == null || password == null || username.equals("") || password.equals("")) {
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("当前输入的用户名或密码不能为空!");
return;
}
// 此处假定用户名只能是 zhangsan 或者 lisi. 密码都是 123
// 正常的登录逻辑, 验证用户名密码都是从数据库读取的.
if (!username.equals("zhangsan") && !username.equals("lisi")) {
// 用户名有问题
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("用户名或密码有误");
return;
}
if (!password.equals("123")) {
// 密码有问题
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("用户名或密码有误");
return;
}
// 3. 用户名和密码验证 ok, 接下来就创建一个会话.
// 当前用户处于未登录的状态, 此时请求的 cookie 中没有 sessionId
// 此处的 getSession 是无法从服务器的 哈希表 中找到该 session 对象的.
// 由于此处把参数设为 true 了, 所以就允许 getSession 在查询不到的时候, 创建新的 session 对象和 sessionId
// 并且会自动的把这个 sessionId 和 session 对象存储的 哈希表 中.
// 同时返回这个 session 对象, 并且在接下来的响应中会自动把这个 sessionId 返回给客户端浏览器.
HttpSession session = req.getSession(true);
// 接下来可以让刚刚创建好的 session 对象存储咱们自定义的数据. 就可以在这个对象中存储用户的身份信息.
session.setAttribute("username", username);
// 4. 登录成功之后, 自动跳转到 主页
resp.sendRedirect("index");
}
}
🍨生成主页的 Servlet:
package login;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
// 这个 Servlet 用来动态的生成主页面.
@WebServlet("/index")
public class IndexServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 此处禁止创建会话. 如果没找到, 认为用户是未登录的状态!!
// 如果找到了才认为是登录状态.
HttpSession session = req.getSession(false);
if (session == null) {
// 未登录状态
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("当前用户未登录!");
return;
}
String username = (String) session.getAttribute("username");
// 这个 ession.getAttribute()方法得到的对象是Object类的,因此,要把它强转为 String 类
if (username == null) {
// 虽然有会话对象, 但是里面没有必要的属性, 也认为是登录状态异常.
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("当前用户未登录!");
return;
}
// 如果上述检查都 ok, 接下来就直接生成一个动态页面.
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write("欢迎你! " + username);
}
}
我们打开页面,
输入账号密码:
点击登录,查看抓包结果:
这个是 login 的请求报文:
此处的请求为post请求,路径为login,提交数据格式和我们前面约定的格式一致。
这个包中并没有Cookie字段。因为此时服务器还不知道是否用户名和密码正确,当用户名和密码正确之后,服务器才会创建Session对象,同时在响应中sessionId发送给浏览器。
这个是 login 的响应报文:
可以看出在相应包里面有一个 Set-cookie 字段。
此时页面成功的跳转到了index页面,也就是说明此时成功登录。
这个是 index 的请求报文:
在抓包结果里面我们就发现了Cookie字段,而这个Cookie字段里面的值和第一个登录时服务器给浏览器返回的set-Cookie里面的SessionId的值一致。服务器就是通过这个sessionId来区分不同的用户的。服务器也就知道是那个用户在访问页面了。
这个是 index 的响应报文:
这就是本篇的全部内容啦~~