news 2026/11/3 20:14:58

Go Web 开发中的 session 与 cookie:原理辨析与 net/http 实战(build-web-application-with-golang 第 6.1 节)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Go Web 开发中的 session 与 cookie:原理辨析与 net/http 实战(build-web-application-with-golang 第 6.1 节)
  • 文档
  • 教程

【免费下载链接】build-web-application-with-golang

A golang ebook intro how to build a web with golang

项目地址:https://gitcode.com/gh_mirrors/bu/build-web-application-with-golang
点击查看免费下载

HTTP 协议天然无状态,如何让服务器识别"上一个请求是谁发来的",是认证页面抓取、登录态保持与访问统计的共同基础。本篇以开源电子书 build-web-application-with-golang 的 zh/06.1.md 为核心,系统讲解 session 与 cookie 的来龙去脉、生命周期分类,以及 Go 语言 net/http 包下设置与读取 cookie 的完整实战代码,并结合本书 6.2 节的会话管理器设计思路(zh/06.2.md)说明两者在服务端认证中的配合方式。读完你不仅能准确辨析这两个概念,还能直接用 Go 写出可运行的 cookie 读写代码,并理解 session 管理器的接口抽象。

为什么需要 session 和 cookie:一个"抓取受限网页"的问题

先看一个具体场景:如何用程序抓取一个访问受限的网页?比如新浪微博好友的主页、个人微博页面。通过浏览器,我们可以手动输入用户名和密码来访问页面;而所谓"抓取",就是使用程序模拟完成同样的工作,因此我们必须先弄清楚"登录"过程中到底发生了什么。

当用户来到微博登录页面,输入用户名和密码后点击"登录",浏览器会将认证信息 POST 给远端的服务器,服务器执行验证逻辑。如果验证通过,浏览器跳转到登录用户的微博首页。但问题来了:登录成功后,服务器如何验证我们对其他受限制页面的访问呢?

因为 HTTP 协议是无状态的,服务器不可能知道用户已经在某次 HTTP 请求中通过了验证。最简单的解决方案自然是"所有请求都带上用户名和密码",但这有两个致命缺陷:

  • 加重服务器负担:每个 request 都需要到数据库验证一次;
  • 降低用户体验:每个页面都需要重新输入用户名密码,每个页面都带有登录表单。

既然在请求中直接携带用户名与密码不可行,就只能在服务器或客户端保存一些可以代表身份的信息,于是就有了 cookie 与 session。

cookie:保存在客户端的历史信息

cookie 简而言之,就是在本地计算机保存用户操作的历史信息(当然也包括登录信息),并在用户再次访问该站点时,浏览器通过 HTTP 协议将本地 cookie 内容发送给服务器,从而完成验证,或继续上一步操作。

其工作原理可以概括为下图所示的流程:服务器通过 Set-Cookie 下发标识,浏览器在后续请求中自动携带,服务器据此识别客户端。

图 6.1 cookie 的原理图

cookie 的本质与存储位置

Cookie 是由浏览器维持的、存储在客户端的一小段文本信息,伴随着用户请求和页面在 Web 服务器和浏览器之间传递。用户每次访问站点时,Web 应用程序都可以读取 cookie 中包含的信息。浏览器设置里通常有 cookie 隐私数据选项,打开后可以看到很多已访问网站保存的 cookies,如下图所示:

图 6.2 浏览器端保存的 cookie 信息

会话 cookie 与持久 cookie:生命周期的两种形态

cookie 是有时间限制的,根据生命期不同分成两种:会话 cookie(session cookie)和持久 cookie(persistent cookie)。

  • 会话 cookie:不设置过期时间,生命周期为"从创建到浏览器关闭为止",只要关闭浏览器窗口 cookie 就消失了。会话 cookie 一般不保存在硬盘上,而是保存在内存里。
  • 持久 cookie:设置了过期时间(例如setMaxAge(60*60*24)),浏览器会把 cookie 保存到硬盘上,关闭后再次打开浏览器依然有效,直到超过设定的过期时间。

需要留意的是,存储在硬盘上的 cookie 可以在不同的浏览器进程间共享(比如两个 IE 窗口);而保存在内存的 cookie,不同的浏览器有不同的处理方式。

session:保存在服务器端的会话状态

与 cookie 相对,session 简而言之就是在服务器上保存用户操作的历史信息。服务器使用 session id 来标识 session,这个 id 由服务器负责产生,保证随机性与唯一性,相当于一个随机密钥,避免在握手或传输中暴露用户真实密码。

但服务器需要把"发送请求的客户端"与"session"对应起来,所以仍要借助 cookie 机制获取客户端的标识(即 session id),也可以通过 GET 方式将 id 提交给服务器。

图 6.3 session 的原理图

session 一词的语义演变

session 中文常翻译为"会话",本来的含义是指有始有终的一系列动作/消息——比如打电话从拿起电话拨号到挂断电话的整个过程可以称之为一个 session。当 session 一词与网络协议相关联时,它往往隐含了"面向连接"和/或"保持状态"这样两个含义。

在 Web 开发环境下,session 的语义又有了新的扩展:它指一类用来在客户端与服务器端之间保持状态的解决方案,有时候也用来指这种解决方案的存储结构。

session 机制的工作流程

session 机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是散列表)来保存信息。当程序需要为某个客户端的请求创建一个 session 时,流程如下:

  1. 服务器首先检查这个客户端的请求里是否包含了一个 session 标识——称为session id;
  2. 如果已经包含 session id,说明以前已经为此客户创建过 session,服务器就按照 session id 把这个 session 检索出来使用。如果检索不到,可能会新建一个(这种情况可能出现在服务端已经删除了该用户对应的 session 对象,但用户人为地在请求的 URL 后面附加了一个类似 JSESSION 的参数);
  3. 如果客户请求不包含 session id,则为此客户创建一个 session,同时生成一个与此 session 相关联的 session id,这个 session id 将在本次响应中返回给客户端保存。

session 机制本身并不复杂,然而其实现和配置上的灵活性却使得具体情况复杂多变。这也要求我们不能把某一次的经验、某一个浏览器或服务器的经验当作普遍适用的结论。

Go 实战:用 net/http 设置 cookie

Go 语言中通过net/http包中的SetCookie来设置 cookie:

http.SetCookie(w ResponseWriter, cookie *Cookie)

其中w表示需要写入的 response,cookie是一个http.Cookie结构体。它的完整定义如下(节选自 Go 标准库 net/http 包):

type Cookie struct { Name string Value string Path string Domain string Expires time.Time RawExpires string // MaxAge=0 means no 'Max-Age' attribute specified. // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0' // MaxAge>0 means Max-Age attribute present and given in seconds MaxAge int Secure bool HttpOnly bool Raw string Unparsed []string // Raw text of unparsed attribute-value pairs }

结构体中的注释给出了MaxAge字段三种取值的语义约定,这是 Go 社区公认的规则:

  • MaxAge = 0:不指定Max-Age属性(即会话 cookie 语义);
  • MaxAge < 0:立即删除 cookie,等价于Max-Age: 0;
  • MaxAge > 0:Max-Age属性存在,数值以秒为单位。

下面是一个完整的设置 cookie 示例——创建一年后过期的 cookie,并写入响应:

expiration := time.Now() expiration = expiration.AddDate(1, 0, 0) cookie := http.Cookie{Name: "username", Value: "astaxie", Expires: expiration} http.SetCookie(w, &cookie)

这里通过Expires设置了过期时间,因此这是一个持久 cookie:浏览器会把它保存到硬盘,一年内重新打开浏览器依然有效。

Go 实战:用 request 读取 cookie

上面的例子演示了如何设置 cookie 数据,接下来演示如何读取 cookie。第一种方式是通过request.Cookie按名称读取单个 cookie:

cookie, _ := r.Cookie("username") fmt.Fprint(w, cookie)

第二种方式是遍历request.Cookies()获取请求中携带的全部 cookie:

for _, cookie := range r.Cookies() { fmt.Fprint(w, cookie.Name) }

可以看到,通过request获取 cookie 非常方便。这两段代码与http.SetCookie配合,就构成了"服务器下发 cookie → 浏览器自动携带 → 服务器读取"的完整闭环——这正是前一节 cookie 原理图的代码级落地。

扩展:从 cookie 到 session 管理器(仓库源码级设计)

cookie 解决了"客户端携带身份标识"的问题,而 session 将真正的会话数据保存在服务端。本书 zh/06.0.md 开篇即点明了这一章的主线:cookie 机制是客户端机制,把用户数据保存在客户端;session 机制是服务器端机制,服务器用类似散列表的结构保存信息,每个访客被分配一个唯一的 sessionID,它要么通过 URL 传递,要么保存在客户端的 cookies 里——你也可以把 Session 保存到数据库,这样更安全,但效率会有所下降。

在此基础上,zh/06.2.md 给出了"Go 如何实现 session"的完整设计。其中最关键的两点与本节内容直接呼应:

  1. session id 的下发方式正是本节讨论的两种机制:一是通过Set-Cookie头把 session 标识符传送到客户端(一般将失效时间设为 0,即会话 cookie,浏览器进程有效);二是 URL 重写,在返回页面的所有 URL 后面追加 session 标识符。后者在客户端禁用 cookie 时是首选方案。
  2. SessionStart 的代码本质就是 cookie 的读写组合。例如 6.2 节中的实现(zh/06.2.md):先调用r.Cookie(manager.cookieName)读取客户端携带的 session id,若不存在则生成新的 id 并通过http.SetCookie写回客户端:
func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session Session) { manager.lock.Lock() defer manager.lock.Unlock() cookie, err := r.Cookie(manager.cookieName) if err != nil || cookie.Value == "" { sid := manager.sessionId() session, _ = manager.provider.SessionInit(sid) cookie := http.Cookie{Name: manager.cookieName, Value: url.QueryEscape(sid), Path: "/", HttpOnly: true, MaxAge: int(manager.maxLifeTime)} http.SetCookie(w, &cookie) } else { sid, _ := url.QueryUnescape(cookie.Value) session, _ = manager.provider.SessionRead(sid) } return }

这段代码把本节的所有知识点串了起来:cookie 只存放 session id(而非用户数据),会话数据保存在服务端由 Provider 管理;HttpOnly: true用于降低 XSS 窃取 cookie 的风险,MaxAge取自管理器配置的maxLifeTime。此外,6.2 节还定义了Provider接口(SessionInit/SessionRead/SessionDestroy/SessionGC)与Session接口(Set/Get/Delete/SessionID),并设计了全局唯一 session id 的生成方式(crypto/rand读取 32 字节后做 base64 编码),感兴趣的读者可以直接阅读 zh/06.2.md 完整代码。

小结:cookie 与 session 的联系与区别

session 和 cookie 的目的相同——都是为了克服 HTTP 协议无状态的缺陷,但完成的方法不同:

  • session:通过 cookie 在客户端保存 session id,而将用户的其他会话消息保存在服务端的 session 对象中;
  • cookie:需要将所有信息都保存在客户端。

因此cookie 存在一定的安全隐患,例如:

  1. 本地 cookie 中保存的用户名密码被破译;
  2. cookie 被其他网站收集,常见途径包括:
    • appA 主动设置域 B 的 cookie,让域 B 获取 cookie;
    • XSS 攻击,在 appA 上通过 JavaScript 获取document.cookie并传递给攻击者的 appB。

在开始 Web 开发之前,有必要将这些基础概念了解清楚,才不会在用到时捉襟见肘,或是在调 bug 时如无头苍蝇乱转。本章接下来的小节(zh/06.2.md、zh/06.3.md 等)将详细介绍 session 的 Go 实现、防劫持与各类存储方案;本章开篇 zh/06.0.md 提供了完整章节导览,全书目录见 zh/preface.md。

  • 文档
  • 教程

【免费下载链接】build-web-application-with-golang

A golang ebook intro how to build a web with golang

项目地址:https://gitcode.com/gh_mirrors/bu/build-web-application-with-golang
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/27 20:55:01

算法备案安全自评估报告写作指南:结构拆解与实操经验

算法备案这个事&#xff0c;做推荐算法、生成式服务、搜索排序的同学这两年应该都不陌生。我自己从第一批备案开始&#xff0c;前后写过近十份安全自评估报告&#xff0c;从最开始反复被退回&#xff0c;到后来基本一次过审&#xff0c;中间踩过的坑确实不少。在这个合规流程里…

作者头像 李华
网站建设 2026/10/28 14:16:11

用claude-mem给Claude装上跨会话记忆,告别重复沟通

如果你跟我一样&#xff0c;几乎天天在用Claude写代码、整理方案、处理文档&#xff0c;大概率撞到过同一堵墙&#xff1a;昨天还在一个会话里聊得清清楚楚的技术选型&#xff0c;今天新建一个会话&#xff0c;Claude完全像失忆了一样&#xff0c;开口又问“你这个项目用什么框…

作者头像 李华
网站建设 2026/10/28 14:50:37

游戏概率设计实战:抽卡保底、PRD算法与玩家心理

聊到游戏中的概率设计&#xff0c;很多人第一反应是抽卡、开箱、暴击这些词。但我在游戏行业摸爬滚打这些年&#xff0c;越来越确信一件事&#xff1a;概率不是"随机数的别名"&#xff0c;它就是游戏设计语言本身。它能让同一套规则&#xff0c;在一千个玩家手里产生…

作者头像 李华
网站建设 2026/10/28 19:06:40

从单机定时任务到分布式任务调度:架构、锁与幂等实战解析

做了快八年后端&#xff0c;聊到任务调度&#xff0c;我总爱说一句&#xff1a;单机任务的终点&#xff0c;就是分布式任务的起点。刚开始工作是拿 Scheduled 定时跑报表&#xff0c;后来慢慢接手订单结算、库存对账、爬虫抓取、缓存刷新&#xff0c;才发现 分布式任务调度系…

作者头像 李华
网站建设 2026/11/3 12:07:46

AI代理技能框架实战:从Claude Code到多代理编排的落地指南

1. 从“superpowers”这个词说起&#xff1a;它到底指什么第一次看到“superpowers”这个标题&#xff0c;加上“agentic skills framework”“software development methodology”这几个关键词&#xff0c;我脑子里第一反应是&#xff1a;这不是某个具体工具的名字&#xff0c…

作者头像 李华
网站建设 2026/10/29 20:58:12

USB2.0眼图测试原理与信号完整性实战指南

1. 什么是USB2.0眼图测试&#xff1f;它到底在测什么&#xff1f;USB2.0眼图测试不是看“眼睛”&#xff0c;而是用示波器把成千上万个高速数据比特叠加显示出来&#xff0c;形成一个类似人眼形状的图形——这就是“眼图”。它本质上是一套时域信号质量的可视化诊断工具&#x…

作者头像 李华