【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不一致,添加的购物信息也是该浏览器客户端独有的:
在这里插入图片描述

Session基本概念

服务器第一次接收到请求时,开辟了一块 Session 空间(创建了Session对象),同时生成一个 sessionId ,并通过响应头的 Set-Cookie:JSESSIONID=XXXXXXX 命令,向客户端发送要求设置 Cookie 的响应; 客户端收到响应后,在本机客户端设置了一个 JSESSIONID=XXXXXXX 的 Cookie 信息,该 Cookie 的过期时间为浏览器会话结束

接下来客户端每次向同一个网站发送请求时,请求头都会带上该 Cookie信息(包含 sessionId ), 然后,服务器通过读取请求头中的 Cookie 信息,获取名称为 JSESSIONID 的值,得到此次请求的 sessionId。

Session生命周期

Session存储在服务器端,一般放置在服务器的内存中(为了高速存取),Session在用户访问第一次访问服务器时创建,需要注意只有访问JSP、Servlet等程序时才会创建Session,只访问HTML、IMAGE等静态资源并不会创建Session,可调用request.getSession(true)强制生成Session。Session什么时候失效?

  1. 服务器会把长时间没有活动的Session从服务器内存中清除,此时Session便失效。Tomcat中Session的默认失效时间为30分钟。
  2. 调用Session的invalidate方法。

Session对浏览器的要求:虽然Session保存在服务器,对客户端是透明的,它的正常运行仍然需要客户端浏览器的支持。这是因为Session需要使用Cookie作为识别标志。HTTP协议是无状态的,Session不能依据HTTP连接来判断是否为同一客户,因此服务器向客户端浏览器发送一个名为JSESSIONID的Cookie,它的值为该Session的id(也就是HttpSession.getId()的返回值)。Session依据该Cookie中的JSESSIONID来识别是否为同一用户
在这里插入图片描述
该Cookie为服务器自动生成的,它的maxAge属性一般为-1,表示仅当前浏览器内有效,并且各浏览器窗口间不共享,关闭浏览器就会失效。

为什么session只能显示在一个浏览器,根源就在cookie,关闭浏览器的时候,会话cookie被关闭,sessionid被丢失,再次访问的时候由于找不到sessionid只能创建新的sessionid,因此同一机器的两个浏览器窗口访问服务器时,会生成两个不同的Session。但是由浏览器窗口内的链接、脚本等打开的新窗口除外。这类子窗口(在链接上右击,在弹出的快捷菜单中选择在新窗口中打"时,子窗口便可以访问父窗口的Session)会共享父窗口的Cookie,因此会共享一个Session,以下是一个子窗口的示例:
在这里插入图片描述
所以说Session并没有被销毁,只是我们打开另一个浏览器使用的是一个新的session,我们用新的session而不用旧的,相当于抛弃了它,实际上它还默默的在服务器端活30分钟。相反request和response每次请求后确确实实被销毁了。

Session常用方法

HttpServletRequest 接口中提供了两个方法来创建 HttpSession 实例,并进行一些简单的操作,通过看Session的源码可以看出:

public interface HttpSession {
    long getCreationTime();

    String getId();

    long getLastAccessedTime();

    ServletContext getServletContext();

    void setMaxInactiveInterval(int var1); //设置 Session 的有效时间

    int getMaxInactiveInterval();

    /** @deprecated */
    @Deprecated
    HttpSessionContext getSessionContext();

    Object getAttribute(String var1);

    /** @deprecated */
    @Deprecated
    Object getValue(String var1);

    Enumeration<String> getAttributeNames();

    /** @deprecated */
    @Deprecated
    String[] getValueNames();

    void setAttribute(String var1, Object var2);

    /** @deprecated */
    @Deprecated
    void putValue(String var1, Object var2);

    void removeAttribute(String var1);

    /** @deprecated */
    @Deprecated
    void removeValue(String var1);

    void invalidate();

    boolean isNew();

Session的缺点

虽然Session很好的避免了Cookie的通信复杂度,但是对于像京东天猫这样服务器就需要记录上亿个session id,这对服务器来说显然不友好,因为线上为了应对大量请求,我们一定是使用服务器集群去分散处理请求的,那么如果用户UA第一次请求了服务器F1,生成了session id,下次请求被路由到了服务器F2,而F2上没有UA的。

  1. 这个时候服务器说,那我这样吧,我把用户请求的服务器记录下来,粘住特定的session id请求到特定的服务器上,这就是session 粘连策略

这样也有风险,万一特定的这个服务器挂了,这个服务器上记录的session就都丢失了,某些特定用户就永远登录不上了

  1. 这个时候服务器说,那这样吧,只要有session产生就复制到各个服务器上,这样就有保障了,这就是session的复制策略

但是这样复制来复制去太麻烦,太耗费性能了,如果有一个统一的机器负责管理session就好了,而且这个统一的机器最好是个集群,这样防止单点挂掉的风险。

  1. 这样就引入了中间件集群这种方式去解决问题,例如引入Redis集群去处理,这就是session 共享策略

但是这样仍然将session置于一个不确定不稳定的状态,因为第三方的集群也可能有宕机的危险

Token机制

那么如果服务端不对session进行记录,只是验证会话有效性呢?也就是客户端第一次请求后服务端依据用户数据计算出一个token签名然后返回给客户端,客户端第二次请求的时候把数据和token签名一起发来,服务端再用相同算法对数据进行签名,得出的token签名一致即认证通过。这就是token的概念

Token解决购物车问题

继续延续上文的购物车问题,有了token之后可以不必考虑session的存储问题,分布式集群问题,按照如下图的流程,我们在
在这里插入图片描述
从上图中可以看出,服务端不存储任何内容,token 只存储在浏览器中,服务端却没有存储,server 会有一套校验机制,校验这个 token 是否合法。那么token既然没有存储到服务端,不像 session 那样根据 sessionId 找到 userid,怎么定位请求的用户呢? 其实token 本身携带 uid 信息。

当然其实我们可以看的出,Token并没有在服务端存储数据,那么怎么记录购物车信息呢,其实Token是一种轻量级的用户认证方案,只能认证当前用户登录的有效性,而并不能追踪用户行为,所以可以说Token在用户验证领域很专业,但是用途比较单一,如果要解决这里的购物车问题,还需要token结合一些数据存储技术,例如数据库或者redis,token负责验证当前用户,数据库或者redis负责进行购物车存取操作。

Token的基本概念

token 验证是无状态的,服务器不记录哪些用户登录了或者哪些 JWT 被发布了,而是每个请求都带上了服务器需要验证的 token,token 放在了 Authorization header 中,形式是 Bearer { JWT },但是也可以在 post body 里发送,甚至作为 query parameter。JWT的结构如下;
在这里插入图片描述
可以看到 token 主要由三部分组成:

  • header:指定了签名算法。
  • payload:可以指定用户 id,过期时间等非敏感数据。
  • Signature:签名,server 根据 header 知道它该用哪种签名算法,再用密钥根据此签名算法对 head+payload 生成签名,这样一个 token 就生成了。

当 server 收到浏览器传过来的 token 时,它会首先取出 token 中的 header+payload,根据密钥生成签名,然后再与 token 中的签名比对,如果成功则说明签名是合法的,即 token 是合法的。而且你会发现 payload 中存有我们的 userId,所以拿到 token 后直接在 payload 中就可获取 userid,避免了像 session 那样要从 redis 去取的开销。

整体的验证流程如下:

  1. 用户输入登录信息
  2. 服务器判断登录信息正确,返回一个 token
  3. token 存储在客户端,大多数通常在 local storage,但是也可以存储在 session storage 或者 cookie 中。
  4. 接着发起请求的时候将 token 放进 Authorization header,或者同样可以通过上面的方式。
  5. 服务器端解码 JWT 然后验证 token,如果 token 有效,则处理该请求。
  6. 一旦用户登出,token 在客户端被销毁,不需要经过服务器端。

可以看的出,token就是一个令牌的概念。

Token的缺点

既然 token 这么好,那为什么我们依然大多数使用场景还是Session?token也有它自身的缺点:

  1. token 太长了,token 是 header,payload 编码后的样式,所以一般要比 sessionId 长很多,很有可能超出 cookie 的大小限制(cookie 一般有大小限制的,如 4kb)。如果你在 token 中存储的信息越长,那么 token 本身也会越长,这样的话由于你每次请求都会带上 token,对请求来是个不小的负担。
  2. token不太安全,我们说 token 是存在浏览器的,既然它太长放在 cookie 里可能导致 cookie 超限,那就只好放在 local storage 里。这样会造成安全隐患,因为 local storage 这类的本地存储是可以被 JS 直接读取的。

所以 token 更适合一次性的命令认证,设置一个比较短的有效期

应用场景的划分

详细的比较下Session、Cookie以及Token的区别和应用场景。

Session和Cookie的使用场景区别

Cookie 和 Session都是⽤来跟踪浏览器⽤户身份的会话⽅式,但是两者的应⽤场景不太⼀样。

  • Cookie ⼀般⽤来保存⽤户信息,通过这种方式不需要用户反复去输入用户信息去登录
    • 我们在 Cookie 中保存已经登录过得⽤户信息,下次访问⽹站的时候⻚⾯可以⾃动帮你登录的⼀些基本信息给填了;
    • ⼀般的⽹站都会有保持登录也就是说下次你再访问⽹站的时候就不需要重新登录了,这是因为⽤户登录的时候我们可以存放了⼀个 Token 在 Cookie中,下次登录的时候只需要根据 Token 值来查找⽤户即可(为了安全考虑,重新登录⼀般要将 Token重写);
    • 登录⼀次⽹站后访问⽹站其他⻚⾯不需要重新登录
  • Session 的主要作⽤就是通过服务端记录⽤户的状态。 典型的场景是购物⻋,当你要添加商品到购物⻋的时候,系统不知道是哪个⽤户操作的,因为 HTTP 协议是⽆状态的。服务端给特定的⽤户创建特定的 Session 之后就可以标识这个⽤户并且跟踪这个⽤户了。
  • Cookie 数据保存在客户端(浏览器端),Session 数据保存在服务器端。Cookie 存储在客户端中,⽽Session存储在服务器上,相对来说 Session 安全性更⾼。如果要在Cookie 中存储⼀些敏感信息,不要直接写⼊ Cookie 中,最好能将 Cookie 信息加密然后使⽤到的时候再去服务器端解密

可以通俗的这么理解,Cookie在客户端保存用户信息,知道用户干了什么事儿,Session则在服务端给用户创建了相应的数据存档,下次只要对应用户登录就能读档,那么要想找到用户正确的访问内容需要二位配合了。

JWT 和 Session+Cookies 基于身份验证的区别

JWT 和 Session Cookies 都提供安全的用户身份验证,但是它们有以下几点不同

  • 密码签名,JWT 具有加密签名,而 Session Cookies 则没有。
  • 连接状态:JWT 是无状态的,因为声明被存储在客户端,而不是服务端内存中。身份验证可以在本地进行,而不是在请求必须通过服务器数据库或类似位置中进行。 这意味着可以对用户进行多次身份验证,而无需与站点或应用程序的数据库进行通信,也无需在此过程中消耗大量资源。
  • 可扩展性,Session Cookies 是存储在服务器内存中,这就意味着如果网站或者应用很大的情况下会耗费大量的资源。由于 JWT 是无状态的,在许多情况下,它们可以节省服务器资源。因此 JWT 要比 Session Cookies 具有更强的可扩展性。
  • 是否可跨域,JWT 支持跨域认证,Session Cookies 只能用在单个节点的域或者它的子域中有效。如果它们尝试通过第三个节点访问,就会被禁止。如果你希望自己的网站和其他站点建立安全连接时,这是一个问题。使用 JWT 可以解决这个问题,使用 JWT 能够通过多个节点进行用户认证,也就是我们常说的跨域认证。

需要说明的是,JWT仅仅是一种用户身份认证的解决方案,如果我们进行购物车的设置,JWT搞不定。

总结一下

这篇Blog参照了很多内容,共写了两天,有很多地方也很纠结没有想通,现在进行一下思路整理吧:

  • cookie就是由服务端生成返回给客户端的,并且在第一次生成时默认给放置了一个Jsession id
  • 浏览器请求服务端时,通过Jsessionid找到对应的session,然后进行对应存取操作,这个查找过程是tomcat帮我们做的,我们在代码里不需要关心,也就是说我们不用调用什么通过sessionid获取session对象之类的方法。
  • 一个客户端浏览器对应一个目标站点【localhost】只产生一个session、一个cookie,我们追踪用户会话,前提也是在这个范围内讨论的,如何跨域生效、如何跨浏览器记忆之类的问题不在讨论范围内,我们只专注于当前客户端浏览器对于当前目标站点是如何进行会话追踪的
  • JSP中的内置对象session就是和Servle中的这个HttpSession生成的,所以之前我们讨论JSP的作用域时其实也大概提了下,只是当时还不知道为什么session的作用域是一个浏览器,并且为什么能跨请求,现在我们知道了,是因为整个浏览器请求目标站点范围内,用的sessionid是同一个,如果cookie一直存在,那么HttpSession就还是那一个,所以作用范围才是这么大。而request只不过是doGet的一个局部变量,当然会请求一次后作用域失效。
  • 对于用户身份验证这种场景,最好用token,因为session操作起来太复杂,没必要为了验证用户身份搭建一套redis,而如果有其它诉求,例如追踪用户的一些信息,还是要使用cookie+session,因为token不在服务端存储数据。
  • cookie+session这种组合用途也要细分,cookie由于存储在客户端本地,所以最好不要放一些敏感信息,而cookie由于存放在客户端本地,所以可以存放很长时间,所以cookie适合防止要求长期存储但不敏感的数据,而session由于运行在服务器内存中或者redis集群中,而且通常有个过期时间,所以适合存放一些短期但重要的数据
  • 当禁用cookie时,需要URL重写技术对Session进行跟踪,这里不做更多讨论
  • 隐藏表单域提交也属于一种会话追踪技术,这里也不做更多的讨论

本篇Blog提到的购物车的例子,由于购物车的信息在各大电商网站比较重要,所以一般会落库并进行数据分析,所以不一定是存储在session或者cookie中,这里只是举个例子,具体以业务场景为准。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

存在morning

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

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

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

打赏作者

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

抵扣说明:

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

余额充值