深入理解Cookie与Session

🎉🎉🎉点进来你就是我的人了
博主主页:🙈🙈🙈戳一戳,欢迎大佬指点!

欢迎志同道合的朋友一起加油喔🤺🤺🤺


目录

一、Cookie和Session(面试常考) 

1. Cookie

2. Session 

3. Cookie和Session是如何一起工作的? 

4. Cookie和Session的过期校验

5. Cookie和Session的关联和区别

二、核心方法

HttpServletRequest 类中的相关方法:

session对象中的常用方法

HttpServletResponse 类中的相关方法:

getCookies()方法

Cookie 类中相关的方法:

Cookie和Session方法使用的总结

三、实现一个用户登录的案例

案例登录淘宝 (演示前后端交互过程) ​编辑

前后端交互中约定的内容

简单实现一个登录

约定前后端的交互接口

1. 代码实现

使用 form表单来构造 post 请求:

LoginServlet 类 (用来构造 POST响应)

IndexServlet 类 (用来构造跳转页面)

2. 代码分析

详解 LoginServlet 类 第1步代码

详解 IndexServlet 类 第1步代码

3. 理解代码交互的过程

4. 展示抓包结果

第一次交互

第二次交互

浏览器展示结果



在开发web应用的过程中,我们经常需要处理客户端和服务器之间的数据交换,以及跟踪用户在应用中的行为。在这里,我将向大家介绍两种重要的技术:Cookie和Session,它们在处理这些问题上起着关键作用。 

一、Cookie和Session(面试常考) 

1. Cookie

  • Cookie是一种浏览器(客户端)提供的持久化存储数据的机制

(1) Cookie从哪里来?是客户端发送请求自带的吗?

  • Cookie中的数据是来自于服务器的,服务器向浏览器发送 HTTP 响应时,它可以在响应头中包含一个或多个 Set-Cookie 头部来设置 Cookie,每一个 Set-Cookie 头部都包含了一个 Cookie 的名称、值以及其它一些属性(如域名、路径、过期时间等)。

(2) Cookie 有什么用? Cookie要到哪里去?

  • Cookie 会在第一次请求的下次请求中,自动被添加到 HTTP请求中的请求头,发给服务器。服务器通过 Cookie 中的内容,以此来验证用户的身份。

(3) Cookie是在哪里存的?

  • Cookie存在浏览器所在主机的硬盘中,Cookie在存的时候是按照 浏览器 + 域名(地址)来进行细分的,不同的浏览器各自存各自的Cookie,同一个浏览器不同的域名对应不同的Cookie,Cookie里的内容不光是键值对还有过期时间      

(4) Cookie保存的数据是什么格式?

  • 多组键值对(键=值,多个键值对用分号间隔)(key = value;)

2. Session 

  • Session是一种服务端保存会话的技术,一次会话指登陆没有注销或者超时

由于HTTP协议是无状态的,所谓无状态,就是一次请求,一次响应,服务端无法感知之前登陆的用户,所以在服务端使用Map<String,Session>的数据结构来保存用户信息

3. Cookie和Session是如何一起工作的? 

以登陆功能举例: 

  1. 服务端校验账号密码成功后,生成一个随机字符串(sessionId用来标识用户身份)及一个Session对象(标识用户的该次对话),把sessionId作为键,Session对象作为值存入Map<String,Session>如果需要保存用户信息就保存在Session对象Map<String,Object>中,相当于登陆时服务端使用Session保存用户信息 
  2. 登陆响应,服务端返回给客户端的HTTP响应数据包中,Set-Cookie响应头包含sessionId=xxx  (相当于给客户端返回了一个身份id)
  3. 客户端收到响应后,保存Cookie信息,将响应的Set-Cookie中的内容保存在客户端本地(和此次服务器地址绑定)
  4. 客户端每次请求时,都携带sessionId=xxx在Cookie头中 (相当于客户端访问带着身份id访问)
  5. 服务端获取客户端请求时,先获取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的关联和区别

关联:

  • 在网站的登陆功能中,需要配合使用.

区别:

  1. Cookie是客户端的存储机制. Session是服务器的存储机制.
  2. Cookie 里面可以存各种键值对(还可以存别的).Session 则专门用来保存用户的身份信息.
  3. Cookie 完全可以单独使用, 不搭配 session (实现非 登陆 场景下)
  4. Session 也可以不搭配 Cookie 使用. (手机 app 登陆服务器, 服务器也需要 Session, 此时就没有Cookie的概念) Cookie跟浏览器强相关的~~
  5. Cookie 是属于 HTTP 协议中的一个部分
  6. 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()的时候具体要做的事情:(默认参数为true,没有会话会自动创建会话)

  • 创建会话
  • 首先会从request对象中获取Cookie 存的 sessionId字段(相当于会话的身份标识),判定这个 sessionId 是否在当前服务器上存在,如果不存在,就会进入到创建会话的逻辑⬇️⬇️⬇️

创建会话: 就会创建一个HttpSession 对象 ,并且生成一个 sessionId(这个sessionId 是一个很长的数字,通常是用十六进制来表示,能够保证唯一性),接下来就会把这个 sessionId 作为 key,把这个Httpsession 对象作为value,把这个键值对给保存到服务器内存的一个"哈希表"(也不一定是他,只是类似这种结构)这样的结构中,再然后服务器就会返回一个HTTP 响应,把 sessionId 通过 Set-Cookie 字段返回给浏览器,浏览器就可以保存这个 sessionId 到 Cookie 中了。        

  • 获取会话
  • 首先会从request对象中获取Cookie 存的 sessionId字段会话的身份标识),判定这个sessionId 是否在当前服务器上存在(也就是在哈希表中是否存在),如果有就直接查询出这个 HttpSession 对象,并且通过返回值返回回去。

那么HttpSession 对象到底是什么呢❓🤔
这个对象本质上也是一个“键值对”的结构,允许程序猿往HttpSession 对象(value)中,存储任意的键值对数据——key必须是String,value是一个Object对象(value部分才是真正存储的数据,这些数据可以是任何类型的Java对象) ,你可以通过相应的键来获取它们。
图解如下:

session对象中的常用方法

  • public Object getAttribute(String name): 返回会话中与给定名字(键)关联的对象(值)。如果会话中没有给定名字的属性,返回null。
  • public Enumeration<String> getAttributeNames(): 返回一个包含会话中所有属性名(即所有的键)的Enumeration对象。
  • public void setAttribute(String name, Object value): 将一个对象(值)绑定到会话中的给定名字(键)。如果会话中已经有给定名字的对象,这个方法将替换掉原来的对象。
  • public void removeAttribute(String name): 从会话中移除给定名字的属性。实际上就是删除会话中的一个键值对。
  • public void invalidate(): 使会话无效,然后解绑它所有的对象。这一般用在用户登出时,使得当前的会话失效。
  • public boolean isNew(): 如果客户端还不知道会话,或者如果客户端选择不加入会话,返回true。例如,如果服务器仅用cookie来维持会话,那么如果客户端禁用了cookie,这个方法将返回true。
  • public void setMaxInactiveInterval(int interval): 设置会话在客户端没有交互的情况下的最大闲置时间(以秒为单位)。超过这个时间,服务器将使会话无效。
  • public int getMaxInactiveInterval(): 返回会话在客户端没有交互的情况下的最大闲置时间(以秒为单位)。
  • public long getCreationTime(): 返回会话被创建的时间(以1970年1月1日午夜(GMT)以来的毫秒数)。
  • public long getLastAccessedTime(): 返回客户端最后一次请求会话所属的servlet的时间(以1970年1月1日午夜(GMT)以来的毫秒数)。
  • public String getId(): 返回一个包含分配给会话的唯一标识符的字符串。这是会话的唯一ID,服务器用它来区分不同的会话。

HttpServletResponse 类中的相关方法:

方法描述
void addCookie(Cookie cookie)添加指定的Cookie到响应头。这将使得浏览器在后续的请求中带上该Cookie
void sendError(int sc)发送一个错误响应到客户端,使用指定的状态码
void sendError(int sc, String msg)发送一个错误响应到客户端,使用指定的状态码和详细消息
void sendRedirect(String location)将客户端重定向到新的位置(URL)
void setStatus(int sc)设置响应的状态码        
void setHeader(String name, String value)设置响应的头部信息
void setContentType(String type)设置响应的内容类型。例如,可以设置为"text/html"或者"application/json"
PrintWriter getWriter()返回一个PrintWriter对象,你可以使用它来向客户端发送文本数据
void setContentLength(int len)设置响应的内容长度。通常,Servlet容器会自动设置这个值,你不需要手动设置
void setCharacterEncoding(String charset)设置响应的字符编码

getCookies()方法

  • 获取到请求中的Cookie数据,返回值是Cookie类型的数组,每个元素是一个Cookie对象,每个Cookie对象又包含了两个属性,name和value(还是键值对形式😂)

HTTP 请求中的 Cookie 字段就是按照键值对的方式来组织的,这里的这些键值对,大概的格式是使用 ; 来分割键值对,使用 = 来分割键和值,这些键值对都会在请求中通过 Cookie 字段传给服务器,服务器收到请求后,就会进行解析,解析成 Cookie[] 这样的形式。
由于Cookie这里是可以保存任意自定制的键值对,所以如果是一般的键值对,直接通过getCookies来获取;如果是特殊的键值对(表示sessionId的键值对),不需要使用getCookies,直接使用getSession其实就自动帮我们从Cookie中取sessionId了。

Cookie 类中相关的方法:

每个 Cookie 对象就是一个键值对

方法描述
String getName()

该方法返回 cookie 的名称。名称在创建后不能改变。(这个值是 SetCooke 字段设置给浏览器的)

String getValue()该方法获取与 cookie 关联的值
void setValue(String newValue)该方法设置与 cookie 关联的值

 Cookie和Session方法使用的总结

  • Cookie和Session的作用:Cookie和Session主要用于跟踪用户的状态。由于HTTP协议本身是无状态的,即服务器默认每次请求都是独立的,因此为了跟踪用户的行为,需要引入Session和Cookie这样的机制。

  • Cookie的基本运作:服务器在响应一个请求时,可以设置一个或多个Cookie,并在HTTP响应头中发送给客户端。客户端会将Cookie保存起来,并在后续的请求中将这些Cookie包含在HTTP请求头中发送给服务器。

  • Session的基本运作:当用户首次访问一个应用时,服务器会为这个用户创建一个Session,并生成一个唯一的Session ID。这个Session ID会被存储在Cookie中并发送给客户端。后续的请求中,客户端会将这个Session ID包含在Cookie中一并发送给服务器,服务器便可根据这个Session ID获取到对应的Session。

  • Session的使用:当服务器创建了一个会话(Session),它可以将任意数据存储在这个会话中。这些数据被存储为键值对(key-value pairs)。每个键值对都代表了一种信息,例如用户的身份信息、购物车信息等。键(key)是唯一的,可以用来获取或者修改对应的值(value)。
  • 以Java Servlet为例,你可以这样操作Session:
// 获取与请求相关联的当前会话对象
HttpSession session = request.getSession(); 
// 将会话中key为 "username" 的值改 key为 "Tom" 的值
session.setAttribute("username", "Tom");  
// 在会话中设置key为 "cart" 所对应的value值为 cartObj 对象
session.setAttribute("cart", cartObj);  

上述代码首先获取了当前请求的Session对象,然后在Session中设置了用户名和购物车对象。这些信息将在整个Session生命周期内保持,除非被删除或修改。

  • 获取和返回Session信息:在处理客户端的请求时,服务器可以根据需要获取Session中的信息。服务器代码负责获取这些信息,以及决定哪些信息将被返回给客户端。
  • 依然以Java Servlet为例,你可以像下面这样获取Session中的信息:
// 获取与请求相关联的当前会话对象
HttpSession session = request.getSession();  
// 从会话中获取key为 "username" 所对应的value值,并将其强制转换为字符串类型
String username = (String) session.getAttribute("username");  
// 从会话中获取key为"cart" 所对应的value值,并将其强制转换为 Cart 对象类型
Cart cart = (Cart) session.getAttribute("cart");  

上述代码首先获取了当前请求的Session对象,然后从Session中分别取出了用户名和购物车对象。你可以根据这些信息来处理请求,例如,你可以检查用户名是否存在,以确认用户是否已经登录;你也可以获取购物车对象,以响应用户对购物车的查询请求。

对于哪些信息应该返回给客户端,这完全取决于你的业务逻辑。例如,如果用户请求查看购物车,那么你可能需要将购物车对象转换为JSON,然后返回给客户端。

  • Session的生命周期:Session在一段时间内没有被使用(例如用户没有发出新的请求)后,会被服务器自动销毁。这个时间间隔可以由开发者设置。此外,服务器也可以在任何时候手动销毁一个Session。当Session被销毁后,相应的用户信息会被清除,用户需要重新登录或者重新设置个人信息等。

三、实现一个用户登录的案例

案例登录淘宝 (演示前后端交互过程) 

服务器以session会话(键值对形式)保存每个用户的信息,键是表示当前用户身份的唯一序号(sessionId),值存储的是用户的信息。后续当浏览器带着唯一身份序号(sessionId)访问时,服务器在会话中按照sessionId获取到对应的session,再从session中按照预先设定的key(key是由程序员在编写代码时定义的。这个键用于标识存储在HttpSession对象中的某个特定的数据)获取用户信息,然后根据这些信息来处理用户的请求,并返回请求的结果给浏览器。

前后端交互中约定的内容

在前后端交互中,通常需要约定以下内容以确保系统的顺利运作:

  1. 请求类型:即 HTTP 方法(如 GET、POST、PUT、DELETE 等)。

  2. 请求 URL:即应用程序的端点(Endpoint),它指明了请求应该发送到哪里。

  3. 请求参数:如果请求中需要包含数据,需要约定使用 Query 参数、Path(URL) 参数,还是 Body 参数,以及这些参数的名称和意义。

  4. 请求头(Request Headers):可能需要约定一些特定的请求头,如 Content-TypeAuthorization 等。

  5. 请求体(Request Body):对于 POST、PUT 等方法,请求体中通常包含了要发送到服务器的数据。需要约定数据的格式(如 JSON、XML 等)和具体的数据结构。

  6. 响应状态码(Response Status Codes):需要约定各种情况下服务器会返回什么样的 HTTP 状态码。

  7. 响应体(Response Body):需要约定服务器返回的数据的格式和具体结构。

  8. 错误处理:需要约定在发生错误时,服务器会返回怎样的错误信息结构,以及前端应如何处理。

  9. 认证和授权:如何处理用户的身份验证和授权,例如使用什么样的认证机制(如 JWT)。

这些都是一般的约定,实际情况可能会根据具体的业务需求和技术选型有所不同。最重要的是,前后端开发团队之间需要有充分的沟通,以确保每一方都清楚地理解了约定内容。

简单实现一个登录
  1. 首先,要有一个 html,包含用户名密码的输入框,以及登录按钮。
  2. 其次,要有一个 LoginServlet,来处理登录请求。
  3. 最后,要有一个 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. 展示抓包结果
第一次交互:
 第二次交互:
 浏览器展示结果

在浏览器为我们呈现的结果就是,当我们输入正确的用户名和密码后,服务器端就会响应,并且跳转到另一个页面。这和我们平时通过网页登录就很相似了,当然,这里,我并没有将前端页面设置的很好看,重在表达逻辑。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

书生-w

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值