您当前的位置:首页 > 电脑百科 > 程序开发 > 编程百科

web 会话机制之 session cookie 详解

时间:2021-01-07 10:02:23  来源:  作者:

老板的苦恼

假如你在繁华的街角开了一家店,每天客人络绎不绝。

不过你作为老板却有一些苦恼,你想知道自己的顾客上一次是什么时候来的?

在店里的时候买了什么商品,方便购物的时候进一步提升用户体验。

可是这些客人赤果果的来,无牵挂的走,店里一直没有留下客人的信息,聪明的你会怎么解决这个问题呢?

web 会话机制之 session cookie 详解

 

输入图片说明

互联网没有记忆

我们常说互联网没有记忆。互联网背后的 HTTP 协议也是如此,正因为它无状态,所以足够简单,便于拓展,得以发展到今天这种局面。

同时也正是因为 HTTP 协议无状态,所以对用户访问等缺乏识别记忆功能。

那怎么解决这个问题呢?

目前有两张最主流的方式:cookie 和 session。

cookie

Cookie 是客户端保存用户信息的一种机制,用来记录用户的一些信息,也是实现 Session 的一种方式。

这个就好比我们把客户上次到店里的时间放在用户的口袋里,下次他们来的时候,我们拿出来看一下,就知道客户上次是什么时候来的了。

当然这些信息用户自己是可以修改的,比如各种浏览器的 cookies 可以被清空。

这让我想起来以前读的一个故事:

刚在路边摊准备买点小吃。我说:老板我经常来买,给我便宜点吧。老板说:我今天第一天摆摊。

铁锅炖自己

信息都放在用户的口袋里虽然方便,但是服务端也要记一些必要的信息,不然被忽悠了都不知道。

session

Session 是在服务端保存的一个数据结构,用来跟踪用户的状态,这个数据可以保存在集群、数据库、文件中;

这个就类似于店里来客人了,服务员留心看一下,知道用户购物车里放了什么商品,是否需要帮助等等。

Cookie 操作

为了让大家直观的感受到 cookie 的使用,我们来看一下 CRUD 的例子。

为了简单,此处使用 servlet 进行演示。

web 会话机制之 session cookie 详解

 

Cookie

说明

Cookie是浏览器保存信息的一种方式,可以理解为一个文件,保存到客户端了啊,服务器可以通过响应浏览器的set-cookie的标头,得到Cookie的信息。

你可以给这个文件设置一个期限,这个期限呢,不会因为浏览器的关闭而消失。

添加

我们可以新建一个 cookie 返回给 resp。

package com.github.houbb.simple.servlet;

import JAVAx.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @author binbin.hou
 * @since 0.0.2
 */
@WebServlet("/cookie/add")
public class CookieAddServlet extends HttpServlet {

    private static final long serialVersionUID = 491287664925808862L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        //1. 创建 cookie 信息
        Cookie cookie = new Cookie("age", "10");
        //30min
        cookie.setMaxAge(30 * 60);
        //2. 返回给客户端,用于客户端保存
        resp.addCookie(cookie);

        //3. 页面输出
        resp.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=utf-8");
        PrintWriter out = resp.getWriter();
        // 后端会根据页面是否禁用 cookie,选择是否将 sessionId 放在 url 后面
        String url = resp.encodeURL("/cookie/get");
        out.println("<a href='"+url+"'>获取 cookie</a>");
    }

}

获取

获取 cookie 也比较简单,直接通过 req.getCookies() 就可以获取到整个 cookie 列表。

package com.github.houbb.simple.servlet;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @author binbin.hou
 * @since 0.0.2
 */
@WebServlet("/cookie/get")
public class CookieGetServlet extends HttpServlet {

    private static final long serialVersionUID = 491287664925808862L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        // 实际的逻辑是在这里
        PrintWriter out = resp.getWriter();

        Cookie[] cookies = req.getCookies();
        if(cookies != null) {
            for(Cookie cookie : cookies) {
                out.println(cookie.getName()+"="+cookie.getValue()+"");
            }
        }
    }

}

删除

cookie 是非法直接删除的,一般都是首先获取,然后设置 maxAge 为 0。

package com.github.houbb.simple.servlet;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 清空
 * @author binbin.hou
 * @since 0.0.2
 */
@WebServlet("/cookie/clear")
public class CookieClearServlet extends HttpServlet {

    private static final long serialVersionUID = 491287664925808862L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        for(Cookie cookie : req.getCookies()) {
            // 立刻失效
            cookie.setMaxAge(0);
            cookie.setPath("/");
            resp.addCookie(cookie);
        }

        resp.setContentType("text/html;charset=utf-8");
        resp.setCharacterEncoding("UTF-8");
        PrintWriter out = resp.getWriter();
        out.println("<a href='/cookie/add'>添加 cookie 信息</a>");
    }

}

session

web 会话机制之 session cookie 详解

 

session

说明

session的实现原理是建立在给浏览器回写cookie,并且是以 JSESSIONID 为键,但是这个cookie是没有时间的,也就是说,当你关闭浏览器时,代表一个会话结束了,也就是说你的session会被删除,当你再次访问服务器的时候,服务器会为你重新创建一个session。

添加

添加 session 属性的方式也比较简单,直接使用 req.getSession().setAttribute("name", "session"); 即可。

package com.github.houbb.simple.servlet;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @author binbin.hou
 * @since 0.0.2
 */
@WebServlet("/session/add")
public class SessionAddServlet extends HttpServlet {

    private static final long serialVersionUID = 491287664925808862L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        // 只有在 getSession 的时候,才会设置对应的 JSESSIONID
        req.getSession().setAttribute("name", "session");

        resp.setContentType("text/html;charset=utf-8");
        resp.setCharacterEncoding("UTF-8");
        PrintWriter out = resp.getWriter();

        // 后端会根据页面是否禁用 cookie,选择是否将 sessionId 放在 url 后面
        String url = resp.encodeURL("/session/get");
        out.println("<a href='"+url+"'>获取 session 信息</a>");
    }

}

获取

我们可以通过 httpSession.getAttributeNames() 获取到所有的 session 属性。

也可以通过 req.getSession().getId() 得到我们的 JSESSIONID 属性。

package com.github.houbb.simple.servlet;

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;
import java.io.PrintWriter;
import java.util.Enumeration;

/**
 * @author binbin.hou
 * @since 0.0.2
 */
@WebServlet("/session/get")
public class SessionGetServlet extends HttpServlet {

    private static final long serialVersionUID = 491287664925808862L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        // 实际的逻辑是在这里
        PrintWriter out = resp.getWriter();
        String jsessionId = req.getSession().getId();
        out.println("jsessionId: " + jsessionId);

        HttpSession httpSession = req.getSession();
        Enumeration attrs = httpSession.getAttributeNames();
        while (attrs.hasMoreElements()) {
            String key = (String) attrs.nextElement();
            Object value = httpSession.getAttribute(key);
            out.println("key: " + key +"; value: " + value);
        }
    }

}

清空

清空 session 的操作非常简单。

直接通过 httpSession.removeAttribute(key) 即可操作。

package com.github.houbb.simple.servlet;

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;
import java.io.PrintWriter;
import java.util.Enumeration;

/**
 * @author binbin.hou
 * @since 0.0.2
 */
@WebServlet("/session/clear")
public class SessionClearServlet extends HttpServlet {

    private static final long serialVersionUID = 491287664925808862L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        resp.setContentType("text/html;charset=utf-8");
        resp.setCharacterEncoding("UTF-8");
        PrintWriter out = resp.getWriter();

        HttpSession httpSession = req.getSession();
        Enumeration attrs = httpSession.getAttributeNames();
        while (attrs.hasMoreElements()) {
            String key = (String) attrs.nextElement();

            httpSession.removeAttribute(key);
            out.println("清空 key: " + key);
        }
    }

}

上面的代码,为了便于大家学习,已经全部开源:

https://gitee.com/houbinbin/simple-servlet

session 的一些细节

相信很多小伙伴读到这里依然是意犹未尽的。

接下来我们一起考虑几个细节问题。

会话机制

session 创建于服务器端,保存于服务器,维护于服务器端,每创建一个新的Session,服务器端都会分配一个唯一的ID,并且把这个ID保存到客户端的Cookie中,保存形式是以 JSESSIONID 来保存的。

一点细节

不要在意

通过HttpServletRequest.getSession 进行获得HttpSession对象,通过setAttribute()给会话赋值,可以通过invalidate()将其失效。

  • 每一个HttpSession有一个唯一的标识SessionID,只要同一次打开的浏览器通过request获取到session都是同一个。
  • WEB容器默认的是用Cookie机制保存SessionID到客户端,并将此Cookie设置为关闭浏览器失效,Cookie名称为:JSESSIONID
  • 每次请求通过读取Cookie中的SessionID获取相对应的Session会话
  • HttpSession的数据保存在服务器端,所以不要保存数据量耗资源很大的数据资源,必要时可以将属性移除或者设置为失效
  • HttpSession可以通过 setMaxInactiveInterval() 设置失效时间(秒)或者在 web.xml 中配置
<session-config>
    <!--单位:分钟-->
    <session-timeout>30</session-timeout>
</session-config>

session 的创建时机

一个常见的误解是以为session在有客户端访问时就被创建,然而事实是直到某server端程序调用 HttpServletRequest.getSession(true) 这样的语句时才被创建。

Session 何时被删除

综合前面的讨论,session 在下列情况下被删除

  1. 程序调用 HttpSession.invalidate();
  2. 距离上一次收到客户端发送的 session id时 间间隔超过了session的超时设置;
  3. 服务器进程被停止(非持久session)

JSESSIONID 的创建与获取

我们在 session 创建的时候,也就是第一次调用 HttpServletRequest.getSession(true) 时,会给客户端分配一个 JSESSIONID 用于唯一标识这个用户。

这个信息会被写回到客户端的 cookie 中,并且后续的请求都会携带。

比如我测试时的 JSESSIONID:

Cookie: JSESSIONID=8AE65FE9AEB0AA6053FADF9ED7AEE544

可以发现实际上 JSESSIONID 是非常依赖客户端 cookie 的,那么问题来了,如果用户禁用了 cookie 怎么办?

客户端禁用 cookie

cookie 是用户自己的口袋,如果用户有一天把口袋全部封死也是有可能的。

如果客户端禁用了 cookie,一般有两种解决方案。

隐藏域

我们将 JSESSIONID 的值传入到页面中,放入一个隐藏的 input 框中,每次请求带上这个参数。

<form name="testform" action="/xxx"> 
  <input type="hidden" name="jsessionid" value="8AE65FE9AEB0AA6053FADF9ED7AEE544"/>
  <input type="text"> 
</form>

后端通过 req.getParameter("jsessionid") 的方式获取到这个 jsessionid 信息。

URL 重写

URL地址重写的原理是将该用户Session的id信息重写到URL地址中。服务器能够解析重写后的URL获取Session的id。

这样即使客户端不支持Cookie,也可以使用Session来记录用户状态。

encodeURL() 方法在使用时,会首先判断Session是否启用,如果未启用,直接返回url。

然后判断客户端是否启用Cookie,如果未启用,则将参数url中加入SessionID信息,然后返回修改的URL;如果启用,直接返回参数url。

就像老马前面代码写的一样:

// 后端会根据页面是否禁用 cookie,选择是否将 sessionId 放在 url 后面
String url = resp.encodeURL("/session/get");
out.println("<a href='"+url+"'>获取 session 信息</a>");

如果我们禁用 cookie,链接的地址就会变成:

http://localhost:8080/session/get;jsessionid=3E2EEB9840F2566EDB3085BA392AE6CB

;jsessionid=3E2EEB9840F2566EDB3085BA392AE6CB 这个是 encodeURL 自己加上去的,这样我们就可以像原来一样处理 session id 了。

连锁店的机遇与挑战

当目前为止,你作为一家店的老板已经可以轻松的掌握客户的信息了。

哪怕用户把自己的口袋封死。

随着你的生意越来越好,你的店从一家门面,变成了多家连锁店。

新的问题又来了,一个用户去了其中的一家,当到另外一家店面的时候,如何得到用户对应的信息呢?

连锁店

这个就涉及到分布式系统的 session 共享问题。

其实解决问题的思路也是从两个角度出发:

(1)用户的角度

在用户的口袋中放着验证信息。

不过需要考虑信息被恶意修改等,这方面 JWT 做的比较优秀。

可以参考:

分布式系统 session 共享解决方案 JWT 实战笔记

(2)服务者的角度

我们作为连锁店,只需要把各个店里的商户信息共享即可。

至于共享到哪里,可以是 redis 也可以是数据库。

这方面 spring session 设计的比较优秀,可以参考:

springboot整合redis实现分布式session

spring session 结合拦截器实战

小结

这一节老马和大家一起学习了 web 会话机制中的 session 和 cookie 机制。

我们知道问题的源头,自然就理解了一个技术产生需要解决的问题。

最后拓展到了分布式系统中的 session 共享问题,后续我们将重点介绍下 spring sesison 和 jwt,感兴趣的小伙伴可以关注一波不迷路。

希望本文对你有所帮助,如果喜欢,欢迎点赞收藏转发一波。

我是老马,期待与你的下次相遇。



Tags:session cookie   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
老板的苦恼假如你在繁华的街角开了一家店,每天客人络绎不绝。不过你作为老板却有一些苦恼,你想知道自己的顾客上一次是什么时候来的?在店里的时候买了什么商品,方便购物的时候进...【详细内容】
2021-01-07  Tags: session cookie  点击:(138)  评论:(0)  加入收藏
▌简易百科推荐
摘 要 (OF作品展示)OF之前介绍了用python实现数据可视化、数据分析及一些小项目,但基本都是后端的知识。想要做一个好看的可视化大屏,我们还要学一些前端的知识(vue),网上有很多比...【详细内容】
2021-12-27  项目与数据管理    Tags:Vue   点击:(1)  评论:(0)  加入收藏
程序是如何被执行的&emsp;&emsp;程序是如何被执行的?许多开发者可能也没法回答这个问题,大多数人更注重的是如何编写程序,却不会太注意编写好的程序是如何被运行,这并不是一个好...【详细内容】
2021-12-23  IT学习日记    Tags:程序   点击:(9)  评论:(0)  加入收藏
阅读收获✔️1. 了解单点登录实现原理✔️2. 掌握快速使用xxl-sso接入单点登录功能一、早期的多系统登录解决方案 单系统登录解决方案的核心是cookie,cookie携带会话id在浏览器...【详细内容】
2021-12-23  程序yuan    Tags:单点登录(   点击:(8)  评论:(0)  加入收藏
下载Eclipse RCP IDE如果你电脑上还没有安装Eclipse,那么请到这里下载对应版本的软件进行安装。具体的安装步骤就不在这赘述了。创建第一个标准Eclipse RCP应用(总共分为六步)1...【详细内容】
2021-12-22  阿福ChrisYuan    Tags:RCP应用   点击:(7)  评论:(0)  加入收藏
今天想简单聊一聊 Token 的 Value Capture,就是币的价值问题。首先说明啊,这个话题包含的内容非常之光,Token 的经济学设计也可以包含诸多问题,所以几乎不可能把这个问题说的清...【详细内容】
2021-12-21  唐少华TSH    Tags:Token   点击:(9)  评论:(0)  加入收藏
实现效果:假如有10条数据,分组展示,默认在当前页面展示4个,点击换一批,从第5个开始继续展示,到最后一组,再重新返回到第一组 data() { return { qList: [], //处理后...【详细内容】
2021-12-17  Mason程    Tags:VUE   点击:(14)  评论:(0)  加入收藏
什么是性能调优?(what) 为什么需要性能调优?(why) 什么时候需要性能调优?(when) 什么地方需要性能调优?(where) 什么时候来进行性能调优?(who) 怎么样进行性能调优?(How) 硬件配...【详细内容】
2021-12-16  软件测试小p    Tags:性能调优   点击:(19)  评论:(0)  加入收藏
Tasker 是一款适用于 Android 设备的高级自动化应用,它可以通过脚本让重复性的操作自动运行,提高效率。 不知道从哪里听说的抖音 app 会导致 OLED 屏幕烧屏。于是就现学现卖,自...【详细内容】
2021-12-15  ITBang    Tags:抖音防烧屏   点击:(23)  评论:(0)  加入收藏
11 月 23 日,Rust Moderation Team(审核团队)在 GitHub 上发布了辞职公告,即刻生效。根据公告,审核团队集体辞职是为了抗议 Rust 核心团队(Core team)在执行社区行为准则和标准上...【详细内容】
2021-12-15  InfoQ    Tags:Rust   点击:(24)  评论:(0)  加入收藏
一个项目的大部分API,测试用例在参数和参数值等信息会有很多相似的地方。我们可以复制API,复制用例来快速生成,然后做细微调整既可以满足我们的测试需求1.复制API:在菜单发布单...【详细内容】
2021-12-14  AutoMeter    Tags:AutoMeter   点击:(20)  评论:(0)  加入收藏
相关文章
    无相关信息
最新更新
栏目热门
栏目头条