【Java Web编程 九】深入理解会话追踪技术Session和Cookie(上)

简介: 【Java Web编程 九】深入理解会话追踪技术Session和Cookie(上)

什么是会话,会话就是从打开浏览器进行一系列操作再到关闭浏览器的全过程。什么是会话追踪技术,就是在一次会话中记录用户的状态,我们知道HTTP协议是无状态的,服务器端接收客户端的请求,生成HTTP响应回发,服务器端关闭连接,当发生一次请求和响应结束后服务器并不能记录信息。

在Web1.0问题这样并没有什么问题,因为大家只是浏览网页而已,但是到了Web2.0时代,也就是交互互联网时代到来后,无状态的HTTP就行不通了,于是就需要会话追踪技术来追踪请求,比如随着网上购物的兴起,需要记录用户的购物车记录(购物车的数据存储),就需要有一个机制记录每个连接的关系,这样我们就知道加入购物车的商品到底属于谁?

有需求才会产生新技术,首先来了解下会话追踪技术的发展历史,这样我们才知道我们的技术是怎样一步一步被需求驱动到现在的(需要特别说明的是,Session,Cookie以及Token我们都默认是浏览器客户端对单个网站应用请求中的行为,而一个站点在一个浏览器中通常只允许一个用户登录,否则后登录的总会覆盖前面先登录的,所以我们可以认为下边的阐述范围是:一个用户对一个网站的请求行为):

Cookie机制

Cookie 是在 HTTP 协议下,维护客户工作站上信息的一种方式。Cookie 是由 Web 服务器保存在用户浏览器上的小文本数据文件,它可以包含有关用户的信息。cookie是不可跨域的,每个cookie都会绑定一个单一的域名,并只能在指定的域名下使用,cookie的作用方式如下:

Cookie解决购物车问题

以当前的购物车问题为例,每次浏览器请求后 server 都会将本次商品 id 存储在 Cookie 中返回给客户端,客户端会将 Cookie 保存在本地,下一次再将上次保存在本地的 Cookie 传给 server 就行了。这样每个 Cookie 都保存着用户的商品 id,购买记录也就不会丢失了

我们在第一次请求的时候除了默认添加的session,还没有任何其它cookie信息:

接下来我们模拟了一个购物行为,在如下Servlet中设置了cookie:

package com.example.MyFirstJavaWeb;
import java.io.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
@WebServlet(name = "helloServlet", value = "/hello-servlet")
public class HelloServlet extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
            Cookie cookie = new Cookie("cart", "cat,dog");     //获取cookie的名字和值,都是string类型
            cookie.setMaxAge(10000);      //设置最大存活时间(在客户端存活),单位是秒,正值表示存活时长(浏览器关闭也没有影响),负值表示浏览器不关就一直存活
            response.addCookie(cookie);    //通过响应把cookie返回给客户端
            Cookie[] cook = request.getCookies();   //通过请求把客户端的cookie返回到服务器
            if (null == cook) {
                return;
            }
            PrintWriter out =response.getWriter();
            for (Cookie c : cook) {
                out.println(c.getName());
                out.println(c.getValue());
            }
    }
    public void destroy() {
    }
}

这样当我们首次请求该购物页面的时候,请求头中还没有cookie:

但是实际的应用程序中我们确看到cookie已经被设置上了,并作为响应cookie返回了:

此时我们再次访问该站点或该网站的其它任意站点都会打印出cookie信息,而且可以在请求头中看到该cookie信息:

Cookie基本概念

HTTP 协议中的 Cookie 包括 Web Cookie 和浏览器 Cookie,它是服务器发送到 Web 浏览器的一小块数据。服务器发送到浏览器的 Cookie,浏览器会进行存储,并与下一个请求一起发送到服务器。通常,它用于判断两个请求是否来自于同一个浏览器,例如用户保持登录状态。Cookie 主要用于下面三个目的

  • 会话管理,登陆、购物车、游戏得分或者服务器应该记住的其他内容
  • 个性化,用户偏好、主题或者其他设置
  • 追踪,记录和分析用户行为

Cookie 曾经用于一般的客户端存储,也是在客户端上存储数据的唯一方法,Cookie 随每个请求一起发送,因此它们可能会降低性能(尤其是对于移动数据连接而言),查看Cookie很简单,以Edge浏览器为例:

Cookie的分类

有两种类型的 Cookies,一种是 Session Cookies,一种是 Persistent Cookies,如果 Cookie 不包含到期日期,则将其视为会话 Cookie。会话 Cookie 存储在内存中,永远不会写入磁盘,当浏览器关闭时,此后 Cookie 将永久丢失。如果 Cookie 包含有效期 ,则将其视为持久性 Cookie。在到期指定的日期,Cookie 将从磁盘中删除。

  • 会话 Cookies,会话 Cookie 有个特征,客户端关闭时 Cookie 会删除,因为它没有指定Expires 或 Max-Age指令。但是,Web 浏览器可能会使用会话还原,这会使大多数会话 Cookie 保持永久状态,就像从未关闭过浏览器一样。
  • 永久性 Cookies,永久性 Cookie 不会在客户端关闭时过期,而是在特定日期(Expires)或特定时间长度(Max-Age)外过期。例如
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2050 07:28:00 GMT;

Cookie的常用方法

Cookie的源码如下,包含一些Cookie的常用方法,例如设置过期时间,域名,安全策略等:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package javax.servlet.http;
import java.io.Serializable;
import java.text.MessageFormat;
import java.util.Locale;
import java.util.ResourceBundle;
public class Cookie implements Cloneable, Serializable {
    private static final long serialVersionUID = -6454587001725327448L;
    private static final String TSPECIALS;
    private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings";
    private static ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.http.LocalStrings");
    private String name;
    private String value;
    private String comment;
    private String domain;
    private int maxAge = -1;
    private String path;
    private boolean secure;
    private int version = 0;
    private boolean isHttpOnly = false;
    public Cookie(String name, String value) {
        if (name != null && name.length() != 0) {
            if (this.isToken(name) && !name.equalsIgnoreCase("Comment") && !name.equalsIgnoreCase("Discard") && !name.equalsIgnoreCase("Domain") && !name.equalsIgnoreCase("Expires") && !name.equalsIgnoreCase("Max-Age") && !name.equalsIgnoreCase("Path") && !name.equalsIgnoreCase("Secure") && !name.equalsIgnoreCase("Version") && !name.startsWith("$")) {
                this.name = name;
                this.value = value;
            } else {
                String errMsg = lStrings.getString("err.cookie_name_is_token");
                Object[] errArgs = new Object[]{name};
                errMsg = MessageFormat.format(errMsg, errArgs);
                throw new IllegalArgumentException(errMsg);
            }
        } else {
            throw new IllegalArgumentException(lStrings.getString("err.cookie_name_blank"));
        }
    }
    public void setComment(String purpose) {
        this.comment = purpose;
    }
    public String getComment() {
        return this.comment;
    }
    public void setDomain(String domain) {
        this.domain = domain.toLowerCase(Locale.ENGLISH);
    }
    public String getDomain() {
        return this.domain;
    }
    public void setMaxAge(int expiry) {
        this.maxAge = expiry;
    }
    public int getMaxAge() {
        return this.maxAge;
    }
    public void setPath(String uri) {
        this.path = uri;
    }
    public String getPath() {
        return this.path;
    }
    public void setSecure(boolean flag) {
        this.secure = flag;
    }
    public boolean getSecure() {
        return this.secure;
    }
    public String getName() {
        return this.name;
    }
    public void setValue(String newValue) {
        this.value = newValue;
    }
    public String getValue() {
        return this.value;
    }
    public int getVersion() {
        return this.version;
    }
    public void setVersion(int v) {
        this.version = v;
    }
    private boolean isToken(String value) {
        int len = value.length();
        for(int i = 0; i < len; ++i) {
            char c = value.charAt(i);
            if (c < ' ' || c >= 127 || TSPECIALS.indexOf(c) != -1) {
                return false;
            }
        }
        return true;
    }
    public Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException var2) {
            throw new RuntimeException(var2.getMessage());
        }
    }
    public void setHttpOnly(boolean isHttpOnly) {
        this.isHttpOnly = isHttpOnly;
    }
    public boolean isHttpOnly() {
        return this.isHttpOnly;
    }
    static {
        if (Boolean.valueOf(System.getProperty("org.glassfish.web.rfc2109_cookie_names_enforced", "true"))) {
            TSPECIALS = "/()<>@,;:\\\"[]?={} \t";
        } else {
            TSPECIALS = ",; ";
        }
    }
}

Cookie的缺点

Cookie有如下的问题,导致一般我们不用Cookie的这种解决方案去追踪重要信息:

  • 每个 cookie的容量有限,为了保证COOKIE不占用太多的磁盘空间,每个COOKIE大小一般不超过4KB
  • 因为cookie由浏览器存储在本地目录,所以不方便记录敏感信息,如密码等
  • cookie不支持跨域访问
  • cookie不支持手机端方案

所以我们要想追踪用户的会话,还需要进行方案改进。

Session机制

客户端请求服务端,服务端会为这次请求开辟一块内存空间,这个对象便是 Session 对象,存储结构为 ConcurrentHashMap。Session 弥补了 HTTP 无状态特性,服务器可以利用 Session 存储客户端在同一个会话期间的一些操作记录

Session解决购物车问题

回到上边的那个例子,随着购物车内的商品越来越多,每次请求的 cookie 也越来越大,这对每个请求来说是一个很大的负担。对于浏览器来说每次请求只是想将其中一个商品加入购物车,但是cookie却将历史记录也保留了,这是个很大的问题。仔细考虑下,由于用户的购物车信息都会保存在 Server 中,所以在 Cookie 里只要保存能识别用户身份的信息,知道是谁发起了加入购物车操作即可。这样每次请求后只要在 Cookie 里带上用户的身份信息,请求体里也只要带上本次加入购物车的商品 id,大大减少了 cookie 的体积大小。

我们把这种能识别哪个请求由哪个用户发起的机制称为 Session(会话机制),生成的能识别用户身份信息的字符串称为 sessionId

它的工作机制如下:

  1. 首先用户登录,server 会为用户生成一个 session,为其分配唯一的 sessionId,这个 sessionId 是与某个用户绑定的。也就是说根据此 sessionid(假设为 abc) 可以查询到它到底是哪个用户,然后将此 sessionid 通过 cookie 传给浏览器。
  2. 之后浏览器的每次添加购物车请求中只要在 cookie 里带上 sessionId=abc 这一个键值对即可,server 根据 sessionId 找到它对应的用户后,把传过来的商品 id 保存到 server 中对应用户的购物车即可。
  3. 用户登出注销该session,当别的用户登录时又给新用户生成新的session和sessionid。

可以看到通过这种方式再也不需要在 cookie 里传所有的购物车的商品 id 了,大大减轻了请求的负担,另外通过上文不难观察出 cookie 是存储在 client 的,而 session 保存在 server,sessionId 需要借助 cookie 的传递才有意义。首先第一次请求该站点时生成Jsessionid:

然后我们在代码里跟踪该客户端的请求:

package com.example.MyFirstJavaWeb;
import java.io.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
@WebServlet(name = "helloServlet", value = "/hello-servlet")
public class HelloServlet extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        HttpSession session =request.getSession();
        response.getWriter().println(session.getId());
        String currentCartItem=request.getParameter("cartItem");
        session.setAttribute("cart", session.getAttribute("cart")+","+currentCartItem);
        response.getWriter().println(session.getAttribute("cart"));
    }
    public void destroy() {
    }
}

请求该站点并向购物车中添加商品时,只需要每次请求时带着当前商品id并且cookie中包含session id即可:

这样我们不必每次请求时带着客户端的信息和全部购物车信息了,只需要带着sessionid即可,当前站点应用程序会通过sessionid自动匹配当前会话对应的session,然后进行数据的存取。这里我们默认一个浏览器客户端对应一个本站点的用户,如果想具体识别某个用户,也可以在登录后把该用户存储到session中即可,登出时销毁session即可。同时我们可以看到从另一个浏览器客户端进入请求时由于sessionid不一致,添加的购物信息也是该浏览器客户端独有的:

相关文章
|
11天前
|
监控 Java 数据管理
java会话跟踪和拦截器过滤器
本文介绍了Web开发中的会话跟踪技术——Cookie与Session,以及过滤器(Filter)和监听器(Listener)的概念和应用。Cookie通过在客户端记录信息来识别用户,而Session则在服务器端保存用户状态。过滤器用于拦截和处理请求及响应,监听器则监控域对象的状态变化。文章详细解释了这些技术的实现方式、应用场景和主要方法,帮助开发者更好地理解和使用这些工具。
27 1
|
2月前
|
存储 缓存 数据处理
php学习笔记-php会话控制,cookie,session的使用,cookie自动登录和session 图书上传信息添加和修改例子-day07
本文介绍了PHP会话控制及Web常用的预定义变量,包括`$_REQUEST`、`$_SERVER`、`$_COOKIE`和`$_SESSION`的用法和示例。涵盖了cookie的创建、使用、删除以及session的工作原理和使用,并通过图书上传的例子演示了session在实际应用中的使用。
php学习笔记-php会话控制,cookie,session的使用,cookie自动登录和session 图书上传信息添加和修改例子-day07
|
2月前
|
安全 Go PHP
Web安全-会话ID漏洞
Web安全-会话ID漏洞
27 3
|
2月前
|
存储 前端开发 Java
JavaWeb基础7——会话技术Cookie&Session
会话技术、Cookie的发送和获取、存活时间、Session钝化与活化、销毁、用户登录注册“记住我”和“验证码”案例
JavaWeb基础7——会话技术Cookie&Session
|
2月前
|
Java API Apache
从零到英雄的蜕变:如何用Apache Wicket打造你的第一个Web应用——不仅是教程,更是编程之旅的启航
【9月更文挑战第4天】学习Apache Wicket这一开源Java Web应用框架是一段激动人心的旅程。本文将指导你通过Maven搭建环境,并创建首个“Hello, World!”应用。从配置`pom.xml`到实现`HelloWorldApplication`类,再到`web.xml`的设置,一步步教你构建与部署简单网页。适合初学者快速上手,体验其简洁API与强大组件化设计的魅力。
58 1
|
3月前
|
存储 关系型数据库 MySQL
PHP编程基础:构建你的第一个Web应用
【8月更文挑战第31天】 在数字时代的海洋里,每个人都可以成为自己命运的船长。本文将引领初学者启航,用PHP语言搭建起第一个属于自己的网站。我们将从浅入深,逐步探索PHP的世界,最终实现一个简单的个人博客系统。这不仅是一段代码的旅程,更是一次思维和技术的飞跃。
|
3月前
|
存储 安全 搜索推荐
【JavaWeb 秘籍】Cookie vs Session:揭秘 Web 会话管理的奥秘与实战指南!
【8月更文挑战第24天】本文以问答形式深入探讨了Web开发中关键的会话管理技术——Cookie与Session。首先解释了两者的基本概念及工作原理,随后对比分析了它们在存储位置、安全性及容量上的差异。接着,通过示例代码详细介绍了如何在JavaWeb环境中实现Cookie与Session的操作,包括创建与读取过程。最后,针对不同应用场景提供了选择使用Cookie或Session的指导建议,并提出了保障二者安全性的措施。阅读本文可帮助开发者更好地理解并应用这两种技术。
58 1
|
3月前
|
网络协议 算法
SYN Cookie技术
【8月更文挑战第18天】
131 4
|
3月前
|
C# 开发者 Windows
WPF遇上Office:一场关于Word与Excel自动化操作的技术盛宴,从环境搭建到代码实战,看WPF如何玩转文档处理的那些事儿
【8月更文挑战第31天】Windows Presentation Foundation (WPF) 是 .NET Framework 的重要组件,以其强大的图形界面和灵活的数据绑定功能著称。本文通过具体示例代码,介绍如何在 WPF 应用中实现 Word 和 Excel 文档的自动化操作,包括文档的读取、编辑和保存等。首先创建 WPF 项目并设计用户界面,然后在 `MainWindow.xaml.cs` 中编写逻辑代码,利用 `Microsoft.Office.Interop` 命名空间实现 Office 文档的自动化处理。文章还提供了注意事项,帮助开发者避免常见问题。
239 0
|
3月前
|
Java Maven Android开发
解锁Web开发新技能:从零开始的Struts 2之旅——让你的Java编程之路更加宽广,首个应用实例带你飞!
【8月更文挑战第31天】对于初学者,掌握 Struts 2 框架不仅能提升 Web 开发能力,还能深入了解 MVC 架构。Struts 2 是一个基于 Servlet 的 Java 框架,提供表单验证、文件上传、国际化等功能,便于快速构建易维护的 Web 应用。本文通过示例演示如何从零开始搭建环境并创建一个简单的 Struts 2 项目,包括配置 `struts.xml`、编写 Action 类及视图文件,并配置 web.xml。通过这些步骤,你将学会基本的开发流程,为进一步学习高级功能打下基础。
48 0

热门文章

最新文章