JavaWeb 学习笔记 6:会话跟踪

news2024/12/24 8:43:32

JavaWeb 学习笔记 6:会话跟踪

HTTP 协议本身是无状态的,所以不能跟踪会话状态。所以会有额外的技术用于跟踪会话:

  • Cookie,客户端技术
  • Session,服务端技术

1.Cookie

1.1.写入 Cookie

可以在服务端通过HttpServletResponse.addCookie向浏览器写入 Cookie:

@WebServlet("/a")
public class ControllerA extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 向浏览器添加 cookie
        Cookie cookie = new Cookie("username", "icexmoon");
        Cookie cookie1 = new Cookie("msg", "hello");
        resp.addCookie(cookie);
        resp.addCookie(cookie1);
    }
}

请求 http://localhost:8080/session-demo/a 能看到响应报文头:

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Set-Cookie: username=icexmoon
Set-Cookie: msg=hello
Content-Length: 0
Date: Mon, 11 Sep 2023 09:39:41 GMT

使用开发者工具可以看到浏览器端 Cookie 已添加:

image-20230911174548444

应当注意到,服务端添加的 Cookie 默认的存活时间(Expire / Max age)默认是会话,即会话结束(关闭浏览器)后 Cookie 就会被销毁。此时 Cookie 仅保存在内存中,并不会被持久化保存(保存到硬盘)。

使用Cookie.setMaxAge可以设置 Cookie 的生存时间(单位:秒):

// 向浏览器添加 cookie
Cookie cookie = new Cookie("username", "icexmoon");
Cookie cookie1 = new Cookie("msg", "hello");
// 设置有效时间为 1 天
cookie.setMaxAge(1 * 24 * 60 * 60);
resp.addCookie(cookie);
resp.addCookie(cookie1);

响应报文:

Set-Cookie: username=icexmoon; Expires=Tue, 12-Sep-2023 09:54:25 GMT
Set-Cookie: msg=hello

响应报文中的 Cookie 有效期是直接以截至时间的方式返回的:

Expires=Tue, 12-Sep-2023 09:54:25 GMT

这是格林尼治时间(GMT),换算成中国时间(东八区)要+8小时。

用开发者工具查看就能看到有效期已经改变:

image-20230911175940484

有效期可以设置为以下几种:

  • 正数,在X秒后过期
  • 0,立即过期(删除)
  • 负数,会话有效期,在会话结束(浏览器退出)后过期

1.2.读取 Cookie

使用HttpServletRequest.getCookies可以读取浏览器传递的 Cookie:

@WebServlet("/b")
public class ControllerB extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Cookie[] cookies = req.getCookies();
        for (Cookie cookie : cookies) {
            if (cookie.getName().equals("username")) {
                String username = cookie.getValue();
                System.out.println("username: " + username);
                break;
            }
        }
    }
}

请求 http://localhost:8080/session-demo/b 就能看到服务端输出的 Cookie 内容。

查看请求报文:

GET /session-demo/b HTTP/1.1
Cookie: JSESSIONID=20C2014C72F0D7ED4D34B821B9A0BC89; username=icexmoon; msg=hello; sentinel_dashboard_cookie=69C1AF3B99482E641CDD23041937F691; JSESSIONID=6EEEF7C596140410E7A21F9DAECF4525
...

当前域名下的所有 Cookie 都以Cookie: xxx=xxx; xxx=xxx 这样的请求头传递。

1.3.中文 Cookie

HTTP 协议规定,报文头内容只能是 ASCII 字符集的字符,所以如果尝试写入中文的 Cookie 信息(UTF-8 字符集)就会报错:

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    Cookie cookie = new Cookie("username", "魔芋红茶");
    response.addCookie(cookie);
}

错误信息:

java.lang.IllegalArgumentException: Control character in cookie value or attribute.

所以要将 UTF-8 字符串转换为全部由 ASCII 字符组成的字符串才能作为 Cookie 内容传递。有多种编码可以实现这一点,最常用的有 URL 编码和 Base64 编码。

这里用 URL 编码举例说明:

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String username = "魔芋红茶";
    username = URLEncoder.encode(username, StandardCharsets.UTF_8.name());
    Cookie cookie = new Cookie("username", username);
    response.addCookie(cookie);
}

响应报文中的信息:

Set-Cookie: username=%E9%AD%94%E8%8A%8B%E7%BA%A2%E8%8C%B6

自然的,在服务端接收到的 Cookie 也是 URL 编码过的,所以需要解码:

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String username = ServletUtil.getCookie(request, "username");
    if (username!=null){
        username = URLDecoder.decode(username, StandardCharsets.UTF_8.name());
    }
    System.out.println(username);
}

2.Session

Session 同样可以用于跟踪会话,并保存会话的状态信息,与 Cookie 不同的是,Session 是服务端技术,保存在服务端。

2.1.写入 Session

@WebServlet("/e")
public class ControllerE extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpSession session = request.getSession();
        session.setAttribute("msg", "Hello World!");
    }
}

2.2.读取 Session

@WebServlet("/f")
public class ControllerF extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpSession session = request.getSession();
        String msg = (String) session.getAttribute("msg");
        System.out.println("Msg in session: " + msg);
    }
}

2.3.实现原理

Session 是基于 Cookie 实现的,浏览器端持有的是作为 Cookie 存储的 SessionID,服务端为每个 SessionID 保存对应的 Session 对象,并且可以用浏览器端用 Cookie 方式传递的 SessionID 获取到对应的 Session 对象。

整个过程可以表示为:

session原理.drawio

根据 Session 的实现原理,Session 的有效期也包含两部分:

  • 浏览器端 SessionID 的有效期
  • 服务器端的 Session 对象的有效期

两者任意一个失效 Session 就不可用了。

浏览器端的 SessionID 的有效期是会话,即关闭浏览器后就失效:

image-20230912123211545

服务器端的 Session 对象由 Web 服务器软件的设置决定,对于 Tomcat,默认的设置为 30 分钟后被清理。需要说明的是,每次有当前会话的请求产生,对应的 Session 对象的过期时间就会刷新,即 +30 分钟。也就是说只要一直有请求,Session 就不会过期,但是如果有超过 30 分钟没有请求,那 Session 对象就会过期被删除。

之所以为 Session 对象设置有效期,是因为 Session 需要占用服务端内存资源。因此尽量不要为 Session 设置过长的有效期。

Tomcat 的默认设置在 /conf/web.xml 中:

<session-config>
    <session-timeout>30</session-timeout>
</session-config>

可以通过修改 Web 应用的 web.xml 覆盖 Tomcat 的默认设置:

<web-app>
  <display-name>Archetype Created Web Application</display-name>
  <session-config>
    <session-timeout>1</session-timeout>
  </session-config>
</web-app>

这样就可以将 Session 对象的过期时间修改为 1 分钟,1 分钟后再请求就会发现对应的 Session 对象已经获取不到了。

也可以用 HttpSession.invalidate方法主动让某个 Session 对象过期。

2.4.Session 的持久化

Session 对象是保存在内存中的,这意味着服务器重启后之前的 Session 对象将不存在。对此,Tomcat 可以在正常退出时将内存中的 Session 序列化后保存在硬盘上,再次启动后从硬盘加载 Session 对象到内存。

非常正常退出,比如关闭线程或者服务器电源关闭等无法持久化保存 Session。

下面用一个简单测试进行验证。

使用命令行mvn tomcat7:run启动 Web 项目。

请求 xxx/e后再请求xxx/f,可以看到 session 已经生成,并且可以读取。

在命令行中按Ctrl+C结束 Tomcat。

注意把 Session 有效期改回 30 秒,并去除相关主动销毁 Session 的代码。

此时会在 Tomcat 下的 localhost/session-demo/org 目录下出现一个序列化文件SESSIONS.ser

image-20230912175034988

重新启动 Tomcat 后,如果需要使用 Session,Tomcat 会将之前的 Session 对象从序列化文件加载,并删除该序列化文件,因此可以访问之前的 Session。

2.5.Session 和 Cookie 的区别

  • 存储位置:Cookie 是将数据存储在客户端,Session 将数据存储在服务端
  • 安全性:Cookie不安全,Session安全
  • 数据大小:Cookie最大3KB,Session无大小限制
  • 存储时间:Cookie可以通过setMaxAge()长期存储,Session默认30分钟
  • 服务器性能:Cookie不占服务器资源,Session占用服务器资源

3.案例:登录注册

登录和验证的实现都比较简单,这里只说明一下验证码的实现。

这里使用一个工具类 CheckCodeUtil 实现验证码的生成:

public class CheckCodeUtil {
    /**
     * 输出随机验证码图片流,并返回验证码值(一般传入输出流,响应response页面端,Web项目用的较多)
     *
     * @param w 宽
     * @param h 高
     * @param os 输出流
     * @param verifySize 验证码位数
     * @return 生成的验证码(字符串)
     * @throws IOException
     */
    public static String outputVerifyImage(int w, int h, OutputStream os, int verifySize) throws IOException {
        String verifyCode = generateVerifyCode(verifySize);
        outputImage(w, h, os, verifyCode);
        return verifyCode;
    }
    // ...
}

利用这个工具类生成验证码,并将生成的验证码图片写入响应报文的输出流:

@WebServlet("/user/check_code")
public class CheckCodeController extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        OutputStream os = response.getOutputStream();
        String checkCode = CheckCodeUtil.outputVerifyImage(100, 50, os, 4);
        request.getSession().setAttribute("checkCode", checkCode);
    }
    // ...
}

用于验证的字符串形式的验证码要保存到 Session,以便在收到注册请求时进行验证。

注册页面用于显示验证码的图片设置src

<tr>
    <td>验证码</td>
    <td class="inputs">
        <input name="checkCode" type="text" id="checkCode">
        <img id="checkCodeImg" src="/login-demo/user/check_code">
        <a href="#" id="changeImg" onclick="refreshCheckCode()">看不清?</a>
    </td>
</tr>

现在页面加载时就能显示验证码。为了能点击 看不清 链接时能刷新,需要实现一个替换图片 src 的 js 方法:

// 刷新验证码
function refreshCheckCode(){
    $("img#checkCodeImg").attr("src","/login-demo/user/check_code");
}

要注意的是,此时只有在开发者工具选择禁用缓存的情况下才能正常刷新验证码,缓存生效时是不会有效果的,因为验证码图片会被缓存起来,浏览器会直接使用缓存,不会再次请求。

这就需要让生成验证码图片的 Servlet 返回的响应报文中禁用缓存:

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // 浏览器不能缓存验证码 Cache-Control: no-cache
    response.setHeader("Cache-Control", "no-store, no-cache, max-age=0, must-revalidate, proxy-revalidate");
    // ...
}

现在就没有类似的问题了。

在客户端发起注册请求时检查验证码:

// 检查验证码是否正确
String checkCode = (String) request.getSession().getAttribute("checkCode");
String inputCheckCode = request.getParameter("checkCode");
if (checkCode == null || inputCheckCode == null){
    throw new RuntimeException("请先输入验证码");
}
if (!checkCode.equalsIgnoreCase(inputCheckCode)){
    System.out.println(checkCode);
    System.out.println(inputCheckCode);
    throw new RuntimeException("验证码不正确");
}

The End,谢谢阅读。

本文的完整示例可以从这里获取。

4.参考资料

  • HTTP 缓存 - HTTP | MDN (mozilla.org)
  • 黑马程序员JavaWeb基础教程

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

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

相关文章

如何使用Spring Security进行身份验证和授权

当您构建一个基于 Spring 框架的 Web 应用程序时&#xff0c;安全性是至关重要的。Spring Security 是 Spring 生态系统中用于处理身份验证和授权的框架。它提供了一种简单而强大的方式来保护您的应用程序&#xff0c;确保只有授权用户才能访问敏感资源。本文将介绍如何使用 Sp…

云计算安全:保护数字资产的前沿策略

文章目录 1. 云计算安全威胁1.1 数据泄露1.2 身份认证问题1.3 无法预测的网络攻击1.4 集中攻击 2. 云计算安全最佳实践2.1 身份和访问管理&#xff08;IAM&#xff09;2.2 数据加密2.3 安全审计和监控2.4 多重身份验证&#xff08;MFA&#xff09; 3. 安全自动化3.1 基础设施即…

【初试433分】中科院859学姐经验分享

这个系列会邀请往届学长学姐进行经验分享~欢迎后台回复经验分享&#xff0c;进行投稿&#xff01; 经验贴征集&#xff1a;前人栽树&#xff0c;后人乘凉&#xff0c;上岸同学也是看着经验贴一点一点过来的&#xff0c;有偿征集各位同学的经验分享&#xff0c;以此来帮助更多的…

一百八十四、大数据离线数仓完整流程——步骤三、在Hive中建基础库维度表并加载MySQL中的维度表数据

一、目的 经过6个月的奋斗&#xff0c;项目的离线数仓部分终于可以上线了&#xff0c;因此整理一下离线数仓的整个流程&#xff0c;既是大家提供一个案例经验&#xff0c;也是对自己近半年的工作进行一个总结。 二、数仓实施步骤 &#xff08;三&#xff09;步骤三、在Hive中…

优化类问题概述

数学建模系列文章&#xff1a; 以下是个人在准备数模国赛时候的一些模型算法和代码整理&#xff0c;有空会不断更新内容&#xff1a; 评价模型&#xff08;一&#xff09;层次分析法&#xff08;AHP&#xff09;,熵权法&#xff0c;TOPSIS分析 及其对应 PYTHON 实现代码和例题…

JVM之选择合适的垃圾收集器(CMS、G1)

1.JVM内存模型&#xff0c;栈、本地方法栈、程序计数器、堆、元空间、方法区、本地方法区&#xff0c;除程序计数器外&#xff0c;其他区域都能进行垃圾收集 2.栈&#xff0c;它的生命周期与线程相同&#xff0c;线程私有&#xff0c;会使用操作系统原生内存&#xff0c;方法…

智慧城市规划与建设中,经常看到的“智慧公厕”是什么?

在智慧城市、智慧机场、智慧园区、智慧服务区、智慧市政、智慧城管、智慧楼宇、智慧旅游等领域&#xff0c;经常看到的智慧公厕究竟是什么&#xff1f;让我们一起来揭秘“智慧公厕”这个常见于智慧城市建设项目的关键词。 从智慧公厕的诞生背景来看&#xff0c;由于智慧城市的…

JAVA 二叉树超详解(1)

树形结构 概念 树是一种非线性的数据结构&#xff0c;它是由n(n>0)个有限结点组成的一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树&#xff0c;也就是说它根朝上&#xff0c;而叶朝下的&#xff0c;具有以下的特点&#xff1a; 1.有一个特殊的结点&…

【C语言】错题本(4)

一. 题目及选项: 答案解析: 知识点: 字符型在内存中的数据存储 char类型数据在内存中的图示: unsigned char类型数据在内存中的图示: 二. 题目及选项: 答案解析: A: B: C: D: 三. 题目及选项: 答案解析: 数据在计算机中是先转换成补码,再进行运算的!

论文笔记:ViTGAN: Training GANs with Vision Transformers

2021 1 intro 论文研究的问题是&#xff1a;ViT是否可以在不使用卷积或池化的情况下完成图像生成任务 即不用CNN&#xff0c;而使用ViT来完成图像生成任务将ViT架构集成到GAN中&#xff0c;发现现有的GAN正则化方法与self-attention机制的交互很差&#xff0c;导致训练过程中…

windows上配置vscode C/C++代码跳转

windows上配置vscode C/C代码跳转 安装插件 C/C 官方的 C/C 插件&#xff0c;必备的插件&#xff0c;是代码跳转、自动补全、代码大纲显示等功能的基础。 Gtags C/C GNU Global GNU Global除了安装该插件之外&#xff0c;还需要在本地下载安装GNU Global工具。多看下插件…

智算创新,美格智能助力智慧支付加速发展

9月21日&#xff0c;以“智算引领创新未来”为主题的紫光展锐2023泛物联网终端生态论坛在深圳举行。作为紫光展锐重要战略合作伙伴&#xff0c;美格智能标准模组产品线总经理郭强华、高级产品总监刘伟鹏受邀出席论坛。美格智能基于紫光展锐5G、4G、智能SoC、Cat.1 bis等芯片平台…

系统集成|第十二章(笔记)

目录 第十二章 沟通管理12.1 沟通的基本概念12.2 主要过程12.2.1 规划沟通管理12.2.2 管理沟通12.2.3 控制沟通 12.3 常见问题 上篇&#xff1a;第十一章、项目人力资源管理 第十二章 沟通管理 沟通管理在项目计划、执行、监控过程中具有重要的作用&#xff0c;项目经理应该拿…

【笔试强训选择题】Day47.习题(错题)解析

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;笔试强训选择题 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01;&#xff01;&#xff…

笔试强训

&#x1f449;&#x1f3fb; Day3 字符串中找出最长的字符串 mycode&#xff1a; #include <iostream> #include<vector>using namespace std;int main() {vector<string> v;string str;getline(cin,str);for(int i 0;i<str.size();i){string s;while(i…

手持式静电场测试仪的功能说明

手持式静电场测试仪是一种便携式的测试仪器&#xff0c;能够快速、准确地测量静电场的强度和分布情况。其主要功能包括&#xff1a; 测量静电场强度&#xff1a;手持式静电场测试仪可以测量静电场的强度&#xff0c;包括静电场的电压、电场强度、电势差等参数。 测量静电电荷&…

(搞定)排序数据结构(1)插入排序 选择排序+冒泡排序

目录 本章内容如下 一:插入排序 1.1插入排序 1.2希尔排序 二&#xff1a;选择排序 2.1选择排序 三:交换排序 3.1冒泡排序 一:插入排序 1.1直接插入排序 说到排序&#xff0c;其实在我们生活中非常常见&…

谈谈最近招人的感受!

最近折腾新的项目&#xff0c;面试了很多实习生小伙伴&#xff0c;我说说我的一些「面试」感受&#xff0c; 虽然是一个老生常谈的话题&#xff0c;但是依然提一下。 准时很重要&#xff1a;提前一点时间&#xff0c;踩个点&#xff0c;别迟到&#xff0c;面试的过程中由于每个…

Python 模拟刮刮乐小游戏

"""刮刮乐小游戏知识点&#xff1a;1、随机模块 random2、嵌套循环 while for3、条件语句/跳转语句 if / continue4、列表添加元素函数 append()"""# 随机模块 import randomwhile True:# 奖品信息prize_info [一等奖, 二等奖, 三等奖, 谢谢惠顾…

交易日均千万订单的存储架构设计与实践 | 京东物流技术团队

一、订单系统概述 1.1 业务范围 服务业务线&#xff1a;快递、快运、中小件、大件、冷链、国际、B2B合同物流、CLPS、京喜、三入三出&#xff08;采购入、退货入、调拨入、销售出、退供出、调拨出&#xff09;等 1.2 订单中心价值 1、解耦&#xff08;提升系统稳定性&#…