从Cookie到Session: Servlet API中的会话管理详解

news2024/11/19 9:28:10

文章目录

  • 一. Cookie与Session
    • 1. Cookie与Session
    • 2. Servlet会话管理操作
  • 二. 登录逻辑的实现

一. Cookie与Session

1. Cookie与Session

首先, 在学习过 HTTP 协议的基础上, 我们需要知道 Cookie 是 HTTP 请求报头中的一个关键字段, 本质上是浏览器在本地存储数据的一种机制, 要清楚 Cookie 是从哪里来, 要到哪里去.

Cookie 是来自于服务器的, 通过响应报文中的 Set-Cookie 字段将数据返回保存在浏览器本地的; 后续当浏览器访问服务器的时候, 就会把本地的 Cookie 通过 HTTP 请求给带过去.

HTTP 协议是 “无状态” 协议, 这里的 “无状态” 指的是默认情况下 HTTP 协议的客户端和服务器之间的每次通信, 和它之前之后的通信是没有直接的联系的, 但是在实际开发中多次通信是需要建立起 “上下文” 联系的, 用户发起请求通过 Cookie 字段将 Cookie 中的内容发送到服务器, 服务器就能知道和客户端的上一次通信处于什么样的状态, 此时 Cookie 这种机制就有了用武之地, 最典型的一种应用, 就是使用 Cookie 来标识用户的身份信息.

常见的网站登录, 比如淘宝, 我们登录一次网站后, 后续再使用访问淘宝的其他页面, 是不需要再次登录的, 还有自动登录功能, 隔了一段时间再次访问淘宝网站, 我们会发现此时并不需要再次输入账号密码登录, 网站就会自动地帮我们登录.

为了实现这种网站自动登录和访问的功能, 就可以将 SessionCookie 搭配使用, 在用户在输入账号密码登录在淘宝服务器查询数据库验证通过后, 服务器会创建一个 Session 会话来保存当前用户的数据和信息, 并生成一个 Cookie 数据.

该 Session 中包含用户一些关 '键身份信息, 服务器会给这个用户分配一个表示身份的序号, 是具有唯一性的整数/字符串 (SessionId), 服务器使用类似于 Hash 表这样的结构把身份序号 (SessionId) 作为 Key, 身份信息 (Session) 作为 Value 存储起来, 这样的每一对键值对就是一个 Session 会话.

服务器给客户端返回的 Cookie 里面就包含 SessionId, 浏览器就会在本地将这个 Cookie 储存起来, 后续浏览器发送请求的时候就会带上这个 Cookie, 服务器收到 Cookie 中的身份序号后, 就会查询 Session 会话表, 如果存在就会可以正常访问, 不用重复的输入账号与密码, 否则就需要用户重新输入账号密码进行登录.

img

有时候我们会发现登录网站后隔一段时间再次登录, 会出现让我们再次输入账号密码的情况, 此时就是登录状态失效过期了, 这种情况可能是可能是客户端把 Cookie 删了, 也可能是服务器这把对应的身份信息删了.

举个生活中里例子, 去医院看病需要先挂号, 如果你是第一次去这家医院的, 就会给你新办理一个就诊卡, 这个就诊卡里面就含有你的一些关键身份信息, 并且会在医院的服务器上新建一个档案, 拿着这个就诊卡, 你就可以在该医院的各个科室进行刷卡, 如果你之前在这个医院有就诊记录, 你一刷卡就可以查询到你所有在医院的就诊信息.

这个例子中的就诊卡就相当于是 Cookie, 上面有你最基本的身份信息, 在医院服务器上所储存有关你的详细信息, 就相当于一个 Session 会话, 服务器上包含很多用户的就诊信息, 即会有多个 Session 会话对应不同的用户.

🎯要注意理解 Cookie 和 Session 之间的区别和关联.

  • 关联: 在网站登录功能中可以搭配使用.
  • 区别:
  1. Cookie 是客户端的存储机制, Session 是服务器的存储机制.
  2. Cookie 里面可以存各种键值对 (还可以存除 SessionId 以外的), Session 则专门用来保存用户信息.
  3. Cookie 完全可以单独使用, 不搭配 Session (实现非登录的场景), Session 也可以不搭配 Cookie (手机 App 登录服务器, 此时也需要 Session, 但这里没有 Cookie 的概念, Cookie 和浏览器强相关的).
  4. Cookie 是 HTTP 协议中的一个部分, Session 则可以和 HTTP 无关 (TCP, WebSocket …也可以用 Session).

2. Servlet会话管理操作

HttpServletRequest 类中, 可以使用 getSession 来获取或者创建会话, getCookies 可以获取请求中的 Cookie 列表.

方法描述
HttpSession getSession()在服务器中获取会话. 参数如果为 true, 则当不存在会话时新建会话; 参数如果为 false, 则当不存在会话时返回 null
Cookie[] getCookies()返回一个数组, 包含客户端发送该请求的所有的 Cookie 对象. 会自动把Cookie 中的格式解析成键值对.

调用 getSession 方法所做的事情:

getSession 有一个 boolean 类型的参数, 如果参数是 true, 它有如下行为:

  1. 读取 cookie 里的 sessionId 字段.
  2. 根据 sessionld 来查询对应的 HttpSession 对象在服务器上是否存在.
  3. 如果不存在, 就创建一个新的会话, 即创建一个新的 HttpSession 对象, 并生成一个唯一的 sessionId, 会以新生成的 sessionId 作为 Key, 生成的 HttpSession 对象作为 Value, 以键值对形式储存到类似于 Hash 的结构中, 然后将 sessionId 设置到响应报文中的 set-Cookie 字段返回给浏览器.
  4. 如果存在就直接返回查询到的 HttpSession 对象.

如果参数是 false, 行为如下:

  1. 读取 cookie 里的 sessionId 字段.
  2. 根据 sessionld 来查询对应的 HttpSession 对象在服务器上是否存在.
  3. 如果不存在, 直接返回 null.
  4. 如果存在就直接返回查询到的 HttpSession 对象.

总之就是, getSession 的参数为true 时允许创建 Session 会话, 为false 时不允许创建 Session 会话.

🍂关于HttpSession
这个对象也可以看作是一个哈希表, 是以键值对的形式存储数据的, 并且允许程序员在对象中储存任意的键值对数据, 但是 Key 必须是 String 类型, Value 的类型是 Object, 设置就比较随意了.

该类中提供了两个方法可以用来获取该对象中键值和设置键值对.

方法描述
Object getAttribute(String name)查询 session 会话中指定键的键值, 查不到则返回 null.
void setAttribute(String name, Object value)绑定一个键和值到该 session 会话中
boolean isNew()判定当前是否是新创建出的会话

所以服务器组织会话的方式就如下图:

img

二. 登录逻辑的实现

我们这里模拟实现一个登录的功能, 很多网站都会让你先登录, 才能使用其中的一些功能, 我们这里实现登录完成之后, 就跳到另一个主页, 不进行登录的话, 这个主页是不能被访问的.

所以, 我们这里主要涉及到两个页面, 第一个是登录页面, 第二个是登录成功后要跳转的主页面.

登录页面包含两个输入框 (用来输入用户名和密码) 和一个登录按钮, 点击登录按钮就会发起一个 HTTP 请求, 服务器处理这个请求的时候就会验证用户名和密码, 验证通过就会跳转到主页, 主页就简单的显示出当前用户的用户名就行了.

实现整这里的逻辑就涉及到两个 Servlet 类:

  1. 处理登录的 LoginServlet, 用来判段用户名和密码, 登录成功和登录失败的情况.
  2. 构造主页面的 IndexServlet, 用来构造登录成功的主页.

img

1️⃣第一步, 约定前后端接口.
我们需要实现两套交互逻辑, 一是登录跳转, 二是获取主页.
登录跳转, 这里只是示范作用, 约定只有一个用户, 就不加数据库的逻辑了.

约定用户名就是 zhangsan, 密码就是 123, 使用 POST 请求, 响应采用 302 重定向.
img
获取主页, 采用 GET 请求, 响应返回一个页面.
img
2️⃣第二步, 编写前端交互页面

目标页面如下:
img
这里的场景很简单. 就直接使用 form 表单构造来 Post 请求了.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>login</title>
</head>
<body>
    <form action="login" method="post">
        <span>用户名</span>
        <input type="text" name="username">
        <span>密码</span>
        <input type="password" name="password">
        <input type="submit" value="登录">
    </form>
</body>
</html>

3️⃣第三步, 编写后端处理代码

对于登录跳转主页的 Post 请求处理思路如下:

  • 从请求中获取用户名和密码.
  • 验证用户名和密码是否正确, 正常来说是要查询数据库的, 这里就不添加数据库的逻辑了.
  • 如果验证通过, 创建会话, 并将 username 和主页被访问的次数数据添加到会话中 (保存必要的用户信息), 创建好会话后, 重定向到主页index.
  • 如果验证不通过, 重定向到登录页面.
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 {
        req.setCharacterEncoding("utf8");
        resp.setCharacterEncoding("utf8");
        // 获取用户名和密码
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        // 验证用户名密码是否正确
        // 这里约定合法的用户名为zhangsan,密码是123

        if (username.equals("zhangsan") && password.equals("123")) {
            // 登陆成功!!
            // 创建一个会话
            HttpSession session = req.getSession(true);
            // 把当前的用户名保存到会话中
            session.setAttribute("username", username);
            // 设置初始情况下登录成功访问主页的次数为0
            session.setAttribute("count", 0);
            // 重定向到主页
            resp.sendRedirect("index");
        } else {
            // 登陆失败!!
            // 重定向到 登陆页面
            System.out.println("用户名或者密码错误!");
            resp.sendRedirect("login.html");
        }
    }
}

获取主页的 GET 请求处理思路:

  • 获取会话 (请求中包含 sessionId, 会话是根据 sessionId 获取的)
  • 取出会话信息, 将主页返回次数加 1 并写回到会话信息中.
  • 返回一个简单的页面, 显示用户名和主页访问次数.
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("/index")
public class IndexServlet extends HttpServlet {
    // 重定向, 浏览器发起的是GET请求
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 先判定用户的登陆状态.
        // 如果用户还没登陆, 要求先登陆.
        // 已经登陆了, 则根据会话中的用户名, 将相关信息显示到页面上.
        HttpSession session = req.getSession(false);
        if (session == null) {
            // 未登录状态
            System.out.println("用户未登录!");
            // 重定向到登录页面
            resp.sendRedirect("login.html");
            return;
        }
        // 已经登陆, 取出会话信息
        String username = (String)session.getAttribute("username");
        Integer count = (Integer)session.getAttribute("count");
        // 访问次数加 1 后再写回到会话中
        count++;
        session.setAttribute("count", count);
        // 构造页面
        resp.setContentType("text/html;charset=utf8");
        resp.getWriter().write("<h3>欢迎您!" + username + "</h3> <h4>这个主页已经被访问了" + count + "次</h4>");
    }
}

抓包分析交互过程:
img
第一次交互, 浏览器从服务器获取登录页面.

img
第二次交互, 浏览器给服务器一个登录请求, 服务器返回响应, 重定向页面.

img
第三次交互, 浏览器收到 302 重定向响应后, 再次向服务器发起请求, 访问主页.

img

响应结果:

img

用户名或者密码错误的情况下:

img

img

未登录直接访问的情况下:

img

img

这里关于 IDEA 集成的 Tomcat 环境有一些需要注意的点, 正常来说, 上面说的 sessionId 并不会一直存在下去, 比如 Tomcat 服务器重新启动的时候, 原来服务器在内存中维护的会话 Hash 表就应该没有了, 此时再次访问, 就应该出现 sessionld 查不到, 就被识别成 “未登录” 状态了, 但是有些版本 Smart Tomcat 为了方便程序猿调试程序, 会在停止服务器的时候把会话持久化保存, 并且在下次启动的时候自动把会话恢复到内存中, 在这种情况下会话是不丢失的.

但如果是手动部署程序到 Tomcat, 则会话默认还是在内存中, 重启服务器是会丢失的.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/633606.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基本类型转换和引用类型转换

文章目录 前言基本类型转换自动数据类型转换强制数据类型转换 引用数据类型转换向上转型向下转型 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; Java 中的类型转换可以分为基本类型转换和引用类型转换两种。 基本类型转换指的是将一种基本数据类型转换…

Koa学习3:用户添加、错误处理

模型 在src目录下创建model目录&#xff0c;用来存放模型 创建用户模型 user.model.js 注意&#xff1a; UUID类型是无法自增的&#xff0c;将id设置为UUID类型时只需要为其指定默认值即可 // 数据类型 const { DataTypes } require(sequelize); // 导入已经连接了数据库…

集合导题、刷题、考试全套完整流程,专业强大的功能,提高刷题学习效率和企业的培训效率

土著刷题微信小程序v1.15&#xff0c;主要是迭代了考试模块的进阶功能&#xff0c;对考试模块进行了一次升级改造。 由于在v1.15开发期间&#xff0c;收到了违规内容整改的通告&#xff0c;为了遵守相关法律法规&#xff0c;让小程序能够平稳安全地运营下去&#xff0c;我们特此…

我说用count(*)统计行数,面试官让我回去等消息...

前言 1 count&#xff08;*&#xff09;为什么性能差&#xff1f; 2 如何优化count&#xff08;*&#xff09;性能&#xff1f; 2.1 增加redis缓存 2.2 加二级缓存 2.3 多线程执行 2.4 减少join的表 2.5 改成ClickHouse 3 count的各种用法性能对比 前言 最近我在公司优…

Consul单机集群

一、准备工作 1、下载consul consul各版本下载地址&#xff0c;点击如下连接前往&#xff1a; Consul Versions | HashiCorp Releases 本案例使用版本&#xff1a;Consul v1.15.0 &#xff1b;下载的文件解压皆可&#xff0c;consul为可执行文件。 2、创建目录&#xff1a…

移动安全app渗透测试之渗透流程、方案及测试要点讲解

被产品经理分到了个app测试的活&#xff0c;&#xff08;话说为啥是产品经理给派活&#xff0c;我不是归技术总监管么&#xff09;&#xff0c;包含安卓端的和ios端的&#xff0c;有点懵逼&#xff0c;说好的web渗透测试和服务器端渗透测试呢&#xff0c;虽然懵逼&#xff0c;不…

【V4L2】v4l2框架分析之video_device

文章目录 &#x1f53a;一、video_device分析&#xff08;1-1&#xff09;struct video_device结构&#xff08;1-2&#xff09;struct v4l2_ioctl_ops结构&#xff08;1-3&#xff09;v4l2_file_operations结构 &#x1f53a;二、注册video设备&#x1f53a;三、卸载清除video…

华为OD机试真题B卷 JavaScript 实现【自守数】,附详细解题思路

一、题目描述 自守数是指一个数的平方的尾数等于该数自身的自然数。例如&#xff1a;25^2 625&#xff0c;76^2 5776&#xff0c;9376^2 87909376。请求出n(包括n)以内的自守数的个数。 数据范围&#xff1a; 1≤n≤10000 二、输入描述 int型整数。 三、输出描述 n以内…

Maven了解及使用

套用一下常用的what, why, how 对maven进行了解。 首先&#xff0c;what&#xff1f; maven是什么&#xff1f; 1、Maven是一个项目管理工具&#xff0c;它包含了一个项目对象模型 (Project Object Model)&#xff0c;一组标准集合&#xff0c;一个项目生命周期(Project Lifecy…

《C# 教程》菜鸟教程学习笔记

学习地址 ######C#有用的网站 C# Programming Guide - 介绍了有关关键的 C# 语言特征以及如何通过 .NET 框架访问 C# 的详细信息。Visual Studio - 下载作为 C# 集成开发环境的 Visual Studio 的最新版本。Go Mono - Mono 是一个允许开发人员简单地创建跨平台应用程序的软件平台…

I.MX6ull GPT高精度定时器

一 简介 GPT的全称是General Purpose Timer&#xff0c;它是一个32位的向上的定时器&#xff0c; GPT 定时器也可以跟一个值进行比较&#xff0c;当计数器值和这个值相等的话就发生比较事件&#xff0c;产生比较中断。GPT 定时器有一个 12 位的分频器&#xff0c;可以对 GPT 定…

sqli-labs靶场通关(1-10)

这次的靶场主要为sql注入的练习&#xff0c;在练习靶场前我们需要了解以下mysql数据库。 数据库是一个用于存储和管理数据的仓库。数据按照特定的格式存储&#xff0c;可以对数据库中的数据进行增加、修改、删除和查询操作。数据库的本质是一个文件系统&#xff0c;按照一定的…

2023年腾讯云618年中大促活动整理汇总

2023年腾讯云618年中大促活动正在进行中&#xff0c;目前正是腾讯云最优惠的时候&#xff0c;小编给大家整理汇总了腾讯云618活动时间、活动入口、活动内容&#xff0c;大家记得抓住上云好时机&#xff01; 一、2023年腾讯云618活动时间 2023年05月25日-2023年6月30日 二、20…

【024】C++对C的扩展之命名空间namespace详解

C对C的扩展 引言一、面向对象编程概述1.1、面向过程1.2、面向对象 二、作用域运算符 :: &#xff08;双冒号&#xff09;三、命名空间 namespace3.1、命名空间使用语法3.2、using声明命名空间中的成员可用3.3、using声明整个命名空间可用 总结 引言 &#x1f4a1; 作者简介&…

【浅谈DBA职业生涯之误操作篇---读书笔记】

&#x1f448;【上一篇】 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 【下一篇】&#x1f449; &#x1f53b;【&#x1f4a3; 话题引入&#xff1a;请列举你在从事 DBA 生涯中,最难以忘怀的一次误操作】 &#x1f6a9; 该话题覆盖…

【算法】深入了解数据压缩算法(无损压缩和有损压缩)

目录 1 引言&#xff1a; 1 数据压缩的重要性和应用场景 2 压缩算法的基本原理和分类 2. 无损压缩算法 2.1 哈夫曼编码 2.1.1 哈夫曼编码的原理和步骤 2.1.2 实现一个简单的哈夫曼编码器 2.2 字典编码 2.2.1 LZW算法的原理和步骤 2.2.2 实现一个基于LZW算法的压缩程序…

力扣笔记(每日随机一题)—— 二叉树的中序遍历

问题&#xff08;简单&#xff09; 给定一个二叉树的根节点 root &#xff0c;返回 它的 中序 遍历 。 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetcode.cn/problems/binary-tree-inorder-traversal/ 示例 1 输入&#xff1a;root […

诡异BUG:DIV 的 margin 设置影响父级

参考资料&#xff1a;1、【web前端】23.解决内部div的margin影响外部div的margin_Anabel Chen的博客-CSDN博客 2、元素之间设置margin的影响及原因 有如下代码&#xff1a; <!doctype html> <html> <head><meta http-equiv"Content-Type" co…

RPC——RPC协议介绍及原理详解

common wx&#xff1a;CodingTechWork 介绍 RPC框架 概念 RPC&#xff08;Remote Procedure Call Protocol&#xff09; 远程过程调用协议。RPC是一种通过网络从远程计算机程序上请求服务&#xff0c;不需要了解底层网络技术的协议。RPC主要作用就是不同的服务间方法调用就…

Java蓝桥杯

目录 往年真题 题目分类 搜索 动态规划 并查集 贪心算法 二分查找 输入输出 图论 其他 往年真题 2022年第十三届蓝桥杯大赛软件类决赛Java研究生组真题 - 题库 - C语言网 2021年蓝桥杯第十二届省赛及国赛真题 - 题库 - C语言网 2020年蓝桥杯第十一届省赛及国赛真题…