🎉🎉🎉点进来你就是我的人了
博主主页:🙈🙈🙈戳一戳,欢迎大佬指点!欢迎志同道合的朋友一起加油喔🤺🤺🤺
目录
一、Cookie和Session(面试常考)
1. Cookie
2 Session
3. Cookie和Session是如何一起工作的?
4. Cookie和Session的过期校验
5. Cookie和Session的关联和区别
关联:
区别:
注意:
二、核心方法
三、交互过程
1. 理解交互过程
2. 解析交互过程
3. 案例登录淘宝(加深理解) 编辑
四、模拟场景实现一个用户登录的案例
约定前后端的交互接口
1. 代码实现
使用 form表单来构造 post 请求:
LoginServlet 类 (用来构造 POST响应)
IndexServlet 类 (用来构造跳转页面)
2. 代码分析
详解 LoginServlet 类 第1步代码
详解 IndexServlet 类 第1步代码
3. 理解代码交互的过程
4. 展示抓包结果
浏览器展示结果
一、Cookie和Session(面试常考)
1. Cookie
Cookie是一种浏览器(客户端)提供的持久化存储数据的机制
- Cookie从哪里来?是客户端发送请求自带的吗?
Cookie中的数据是来自于服务器的,服务器向浏览器发送 HTTP 响应时,它可以在响应头中包含一个或多个
Set-Cookie
头部来设置 Cookie,每一个Set-Cookie
头部都包含了一个 Cookie 的名称、值以及其它一些属性(如域名、路径、过期时间等)。
- Cookie 有什么用? Cookie要到哪里去?
Cookie 会在第一次请求的下次请求中,自动被添加到 HTTP请求中的请求头,发给服务器。服务器通过 Cookie 中的内容,以此来验证用户的身份。
- Cookie是在哪里存的?
Cookie存在浏览器所在主机的硬盘中,Cookie在存的时候是按照 浏览器 + 域名(地址)来进行细分的,不同的浏览器各自存各自的Cookie,同一个浏览器不同的域名对应不同的Cookie,Cookie里的内容不光是键值对还有过期时间
- Cookie保存的数据是什么格式?
多组键值对(键=值,多个键值对用分号间隔)(key = value;)
2 Session
Session是一种服务端保存会话的技术,一次会话指登陆没有注销或者超时
由于HTTP协议是无状态的,所谓无状态,就是一次请求,一次响应,服务端无法感知之前登陆的用户,所以在服务端使用Map<String,Session>的数据结构来保存用户信息
3. Cookie和Session是如何一起工作的?
以登陆功能举例:
- 服务端校验账号密码成功后,生成一个随机字符串(sessionId用来标识用户身份)及一个Session对象(标识用户的该次对话),把sessionId作为键,Session对象作为值存入Map<String,Session>如果需要保存用户信息就保存在Session对象Map<String,Object>中,相当于登陆时服务端使用Session保存用户信息
- 登陆响应,服务端返回给客户端的HTTP响应数据包中,Set-Cookie响应头包含sessionId=xxx (相当于给客户端返回了一个身份id)
- 客户端收到响应后,保存Cookie信息,将响应的Set-Cookie中的内容保存在客户端本地(和此次服务器地址绑定)
- 客户端每次请求时,都携带sessionId=xxx在Cookie头中 (相当于客户端访问带着身份id访问)
- 服务端获取客户端请求时,先获取Cookie请求头中的内容,查找sessionId对应的值,然后从保存的Map结构中查找,如果存在就是登录用户,如果为null就是未登录 (服务器通过身份id确定客户端的身份,将保存的用户信息返回给客户端)
4. Cookie和Session的过期校验
🌵Session的过期校验
服务端保存的Session信息有默认的过期时间(可通过程序设置)
服务器有Session的过期校验机制:通过单独的线程扫描,发现当前时间和Session最后一次使用的时间超时就删掉
服务器存放Session的地方,web服务器默认是存放在内存中,所以重启服务器Session也就没了,但是有些服务器把数据保存在服务器硬盘,重启就还有
如果用户注销登录,相当于服务端删除Map中的Session
所以超时后,注销后,重启服务器后需要访问页面就需要重新登陆
🌳Cookie的过期校验
Cookie也有过期时间(可以通过程序设置)
如果Cookie过期,浏览器发请求时就不会携带这些信息,服务端验证sessionId时就会验证失败,也就是没有登陆
如果在客户端手动删除Cookie,就相当于服务端还有Session信息,但是客户端请求时也不会携带Cookie信息,服务端验证sessionId失败,也就意味没有登陆
5. Cookie和Session的关联和区别
关联:
- 在网站的登陆功能中,需要配合使用.
区别:
- Cookie是客户端的存储机制. Session是服务器的存储机制.
- Cookie 里面可以存各种键值对(还可以存别的).Session 则专门用来保存用户的身份信息.
- Cookie 完全可以单独使用, 不搭配 session (实现非 登陆 场景下)
- Session 也可以不搭配 Cookie 使用. (手机 app 登陆服务器, 服务器也需要 Session, 此时就没有Cookie的概念) Cookie跟浏览器强相关的~~
- Cookie 是属于 HTTP 协议中的一个部分
- Session 则可以和HTTP 无关(TCP, websocket .. 也可以用 session)
注意:
- 此处 Cookie 和 Session 之间的配合,主要是针对一些主流的网页实现。Cookie 字段存在的意义就是:为了浏览器能够安全可靠地访问服务器的存储信息。在很多情况下,服务器会生成一个唯一的Session ID,并将其存储在一个Cookie中发送给浏览器。这样,当浏览器再次向服务器发送请求时,服务器可以通过读取Session ID来查找对应的Session
二、核心方法
HttpServletRequest 类中的相关方法:
方法 | 描述 |
---|---|
HttpSession getSession() | 在服务器中获取会话. 参数如果为 true, 则当不存在会话时新建会话; 参数如果为 false, 则当不存在会话时返回 null |
Cookie[] getCookies() | 返回一个数组, 包含客户端发送该请求的所有的 Cookie 对象. 会自动把Cookie 中的格式解析成键值对. |
getSession 方法:既能用于获取到服务器上的会话,也能用于创建会话。具体行为取决于参数:
- 如果参数为true:若会话不存在,则创建;若会话存在,则获取
- 如果参数为false:若会话不存在,则返回null;若会话存在,则获取
就还拿之前那个医院看病的例子来说:
当你来到一家新医院的时候,你要先挂号,那么人家挂号的医生就会问你,你有没有这里的"就诊卡"?有就不需要再办了,如果没有就需要办一张新的卡。而当你挂了号之后,你不仅会得到一张"就诊卡",同时在医院的服务器里也会给你开一个档案来存储你的就诊信息(这就是你的会话)。
但是如果这个医院比较特殊,人流量特别多的话,而这个医院的承载病人的能力是有上限的,那么这个时候医生就会问你在这里建过档没有,如果建过才会给你挂号,没有建过的话就不给你挂(就像生孩子的时候得提前预约(当你发现自己怀孕了就提前去建个档),因为妇产科人是特别多的!)
调用getSession()的时候具体要做的事情:
- 创建会话
- 首先先获取到请求中 Cookie 里面的 sessionId字段(相当于会话的身份标识),判定这个 sessionId 是否在当前服务器上存在,如果不存在,就会进入到创建会话的逻辑⬇️⬇️⬇️
创建会话: 就会创建一个HttpSession 对象 ,并且生成一个 sessionId(这个sessionId 是一个很长的数字,通常是用十六进制来表示,能够保证唯一性),接下来就会把这个 sessionId 作为 key,把这个Httpsession 对象作为value,把这个键值对给保存到服务器内存的一个"哈希表"(也不一定是他,只是类似这种结构)这样的结构中,再然后服务器就会返回一个HTTP 响应,把 sessionId 通过 Set-Cookie 字段返回给浏览器,浏览器就可以保存这个 sessionId 到 Cookie 中了。
- 获取会话
- 先获取到请求中的 Cookie 里面的 sessionId字段(会话的身份标识),判定这个sessionId 是否在当前服务器上存在(也就是在哈希表中是否存在),如果有就直接查询出这个 HttpSession 对象,并且通过返回值返回回去。
那么HttpSession 对象到底是什么呢❓🤔
这个对象本质上也是一个“键值对”的结构,允许程序猿往HttpSession 对象(value)中,存储任意的键值对数据——key必须是String,value是一个Object
图解如下:
getCookies()方法:获取到请求中的Cookie数据,返回值是Cookie类型的数组,每个元素是一个Cookie对象,每个Cookie对象又包含了两个属性,name和value(还是键值对形式😂)
HTTP 请求中的 Cookie 字段就是按照键值对的方式来组织的,这里的这些键值对,大概的格式是使用 ; 来分割键值对,使用 = 来分割键和值,这些键值对都会在请求中通过 Cookie 字段传给服务器,服务器收到请求后,就会进行解析,解析成 Cookie[] 这样的形式。
由于Cookie这里是可以保存任意自定制的键值对,所以如果是一般的键值对,直接通过getCookies来获取;如果是特殊的键值对(表示sessionId的键值对),不需要使用getCookies,直接使用getSession其实就自动帮我们从Cookie中取sessionId了。
HttpServletResponse 类中的相关方法:
方法 | 描述 |
---|---|
void addCookie(Cookie cookie) | 把指定的 cookie 添加到响应中 |
Cookie 类中相关的方法:
每个 Cookie 对象就是一个键值对
方法 | 描述 |
---|---|
String getName() | 该方法返回 cookie 的名称。名称在创建后不能改变。(这个值是 SetCooke 字段设置给浏览器的) |
String getValue() | 该方法获取与 cookie 关联的值 |
void setValue(String newValue) | 该方法设置与 cookie 关联的值 |
三、交互过程
1. 理解交互过程
在下图中,用户 Tom 从浏览器登录,那么浏览器就要给服务器返回响应,假设登录成功,服务器就会返回登录成功的响应,那么第二次 Tom 想获取购物车的请求,服务器是怎么知道的,我即将访问的购物车数据是 Tom 的呢?
2. 解析交互过程
3. 案例登录淘宝(加深理解)
服务器以key,value的键值对形式保存每个用户的信息,key是表示当前用户身份的序号(唯一),value存储的用户的信息,后续浏览器访问带着唯一身份序号(sessionId)访问,服务器在上述hash表中按照key去查询对应的value,将用户信息返回给浏览器
四、模拟场景
实现一个用户登录的案例
- 首先,要有一个 html,包含用户名密码的输入框,以及登录按钮。
- 其次,要有一个 LoginServlet,来处理登录请求。
- 最后,要有一个 IndexServlet,模拟登录完成后,跳转到的主页,在这个主页里面就能够获取到当前用户的身份信息。 ( 这里就可以存储开发人员自定义的用户数据,比如可以存一个当前用户访问的次数 )
约定前后端的交互接口
我们这里需要两组交互:一个是登录,另一个是获取主页。而针对前后端交互接口的话,实际上有很多种约定方式,我们选择下面这种来进行约定
1. 代码实现
之前说了一些理论的交互流程,而 Servlet 已经对这里的流程进行了封装,开发人员在使用的时候,相对简单,因为大部分工作,已经在 Servlet 的内部封装好了,只需要通过 一些API 进行调用即可。
使用 form表单来构造 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'>
<br>
<input type="password" name="password">
<br>
<input type="submit" value="提交">
</form>
</body>
</html>
LoginServlet 类 (用来构造 POST响应)
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter("username");
String password = req.getParameter("password");
//验证用户名密码是否正确
//正确情况下,用户名和密码是用数据库来保存
//此处直接写死
//此处约定, 用户名合法的是 zhangsan 和 lisi
//密码合法的都是 123
/*//这个代码固然能写出效果,但是嵌套太深了,还是等价转换一下
if(username.equals("zhangsan") || username.equals("lisi")) {
if(password.equals("123")) {
//登录成功
} else {
//登录失败
}
} else {
//登录失败
}*/
if(!username.equals("zhangsan") && !username.equals("lisi")) {
//登录失败 -> 重定向到 登录页面
System.out.println("登录失败,用户名错误");
resp.sendRedirect("login.html");
return;
}
if(!password.equals("123")) {
//登录失败 -> 重定向到 登录页面
System.out.println("登录失败,m密码错误");
resp.sendRedirect("login.html");
return;
}
//登录成功
//1. 创建一个会话
HttpSession session = req.getSession(true);
//2. 把当前的用户名保存到会话中
session.setAttribute("username",username);
//3. 重定向到主页
resp.sendRedirect("index");
}
}
IndexServlet 类 (用来构造跳转页面)
@WebServlet("/index")
public class IndexServlet extends HttpServlet {
//通过重定向,浏览器发送的是 GET.
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//先判断用户的登录状态
//如果用户还没登录,要求先登录
//已经登录了,根据 会话 中的用户名,来显示到页面上
//1.这个操作不会触发会话的创建
HttpSession session = req.getSession(false);
if(session == null) {
//未登录状态
System.out.println("用户未登录!");
resp.sendRedirect("login.html");
return;
}
//2.已经登录
String username = (String) session.getAttribute("username");
//3.构造页面
resp.setContentType("text/html;charSet=utf8");
resp.getWriter().write("欢迎" + username + "回来!");
}
}
2. 代码分析
详解 LoginServlet 类 第1步代码
HttpSession session = req.getSession(true);
上面的代码表示:session 这个会话在第一次登录成功之前,并没有创建,由于 getSession 方法的参数是 true,所以登录成功后,【不存在会话,就创建。】即创建了一个 session 对象,同时生成了一个 sessionID.
只不过,我们在代码中看不到生成的 sessionID 是什么,这是因为 Servlet 已经为我们封装好了代码。
详解 IndexServlet 类 第1步代码
HttpSession session = req.getSession(false);
上面的代码表示:通过 session 对象这个会话机制,来判定 session 对象有没有被创建出来,以此来判定用户是否已经登录过页面。
由于 getSession 方法中的参数是 false,【不存在,不创建】。既然不创建,也就只能用来判断用户是否已然是登录状态了。( 在 LoginServlet 类中,我们可以看到:只要用户名或密码输入错误,也就意味着登录失败,代码的逻辑是什么也不做,最终也就返回了,故而,session 这个对象就没有被创建出来,显然,用户没有成功登录。)
如果登录成功,说明 session 对象早已经被创建了,那么它的里面也就存储了键值对结构,包括用户名和密码。
后续,如果我们通过值来找到对象,或者通过对象找值,也就很方便了。总之,我们就将其看作键值对的结构即可。
注意:
上面的两个不同类中的 session 表示的是同一个对象,也就是说,session 只有一份,我们可以将它想象成一个存钱罐,【存钱的时候,就往里面放对象;取钱的时候,就从里面拿对象。】
3. 理解代码交互的过程
我们将 Cookie 想象成一个身份令牌,同一个账户只有唯一一个令牌,每个令牌对应的sessionID 不相同,所以每个账户的令牌都不相同。那么,每个账户对应的 Cookie 也不相同,这也就实现了网页登录的安全性。此外,在第一次登录之后,客户端发送第二次请求的时候,就会自动带上 Cookie,这样一来,服务器就能直接通过 Cookie 中的 sessionID 就行识别,之后的操作,服务器就只对当前用户进行数据访问了。
4. 展示抓包结果
第一次交互:
第二次交互:
浏览器展示结果
在浏览器为我们呈现的结果就是,当我们输入正确的用户名和密码后,服务器端就会响应,并且跳转到另一个页面。这和我们平时通过网页登录就很相似了,当然,这里,我并没有将前端页面设置的很好看,重在表达逻辑。