- 文档
- 教程
【免费下载链接】build-web-application-with-golang
A golang ebook intro how to build a web 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 时,流程如下:
- 服务器首先检查这个客户端的请求里是否包含了一个 session 标识——称为session id;
- 如果已经包含 session id,说明以前已经为此客户创建过 session,服务器就按照 session id 把这个 session 检索出来使用。如果检索不到,可能会新建一个(这种情况可能出现在服务端已经删除了该用户对应的 session 对象,但用户人为地在请求的 URL 后面附加了一个类似 JSESSION 的参数);
- 如果客户请求不包含 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"的完整设计。其中最关键的两点与本节内容直接呼应:
- session id 的下发方式正是本节讨论的两种机制:一是通过
Set-Cookie头把 session 标识符传送到客户端(一般将失效时间设为 0,即会话 cookie,浏览器进程有效);二是 URL 重写,在返回页面的所有 URL 后面追加 session 标识符。后者在客户端禁用 cookie 时是首选方案。 - 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 存在一定的安全隐患,例如:
- 本地 cookie 中保存的用户名密码被破译;
- 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
相关推荐
Go Web 开发中的 Session 与 Cookie:原理、区别与 net/http 实践(Build Web Application with Golang 第 6.1 节)
Go Web 开发中的 Session 与 Cookie:原理、区别与 net/http 实践(Build Web Application with Golan
文档教程Go Web 开发中的 Cookie 与 Session:原理、net/http 实操与安全要点(build-web-application-with-golang 第 6.1 节)
Go Web 开发中的 Cookie 与 Session:原理、net/http 实操与安全要点(build web application with gola
文档教程build-web-application-with-golang 第 6.1 节精读:Cookie 与 Session 原理及 Go 实战
build web application with golang 第 6.1 节精读:Cookie 与 Session 原理及 Go 实战 导读 HTTP 是
文档教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考