Session与Cookie的个人理解

208 阅读7分钟

首先大家知道,http协议是无状态的,即你连续访问某个网页100次和访问1次对服务器来说是没有区别对待的,因为它记不住你。  那么,在一些场合,确实需要服务器记住当前用户怎么办?比如用户登录邮箱后,接下来要收邮件、写邮件,总不能每次操作都让用户输入用户名和密码吧,为了解决这个问题,session的方案就被提了出来,事实上它并不是什么新技术,而且也不能脱离http协议以及任何现有的web技术

假设你访问网页时就像逛澡堂,第一次进去你是没有钥匙的,这个时候你交了钱服务台就分配一把钥匙给你,你走到哪里都要带上,因为这是你身份的唯一标识sessionid,接下来你用这把钥匙可以去打开一个专有的储物柜存储你的衣物,游完泳,你再用钥匙去打开柜子拿出衣物,最后离开游泳池时,把钥匙归还,你的这次游泳的过程就是一次session,或者叫做会话,在这个例子中,钥匙就是session的key,而储物柜可以理解为存储用户会话信息的介质。 原理很简单,假设你访问网页时就像逛澡堂,第一次进去你是没有钥匙的,这个时候你交了钱服务台就分配一把钥匙给你,你走到哪里都要带上,因为这是你身份的唯一标识sessionid,接下来你用这把钥匙可以去打开一个专有的储物柜存储你的衣物,游完泳,你再用钥匙去打开柜子拿出衣物,最后离开游泳池时,把钥匙归还,你的这次游泳的过程就是一次session,或者叫做会话,在这个例子中,钥匙就是session的key,而储物柜可以理解为存储用户会话信息的介质。

  那么在web server中如何实现session呢?想必看了上面的例子你会很容易理解,主要是解决两个问题,一个是钥匙的问题,一个是存储用户信息的问题

对于第一个问题,即什么东西可以让你每次请求都会自动带到服务器呢?如果你比较了解http协议,那么答案一目了然,就是cookie,如果你想为用户建立一次会话,可以在用户授权成功时给他一个cookie,叫做会话id,它当然是唯一的,值看起来为一个随机字符串的cookie,如果下次发现用户带了这个cookie,服务器就知道,哎呀,刚刚这位顾客来了

SESSION 的数据保存在哪里呢?

cookie 机制是一种客户端机制,把用户数据保存在客户端,而 session 机制是一种服务器端的机制,服务器使用一种类似于散列表的结构来保存信息,每一个网站访客都会被分配给一个唯一的标志符,即 sessionID, 它的存放形式无非两种:要么经过 url 传递,要么保存在客户端的 cookies 里。当然,你也可以将 Session 保存到数据库里,例如redis存储重要的用户信息,这样会更安全,但效率方面会有所下降。


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

如果不设置过期时间,则表示这个 cookie的生命周期为从创建到浏览器关闭为止,只要关闭浏览器窗口,cookie 就消失了。这种生命期为浏览会话期的 cookie 被称为会话 cookie。会话 cookie 一般不保存在硬盘上而是保存在内存里。

如果设置了过期时间 (setMaxAge(60x60x24)),浏览器就会把 cookie 保存到硬盘上,关闭后再次打开浏览器,这些 cookie 依然有效直到超过设定的过期时间。存储在硬盘上的 cookie 可以在不同的浏览器进程间共享,比如两个 IE 窗口。而对于保存在内存的 cookie,不同的浏览器有不同的处理方式。

Go 设置 cookie

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

http.SetCookie(w ResponseWriter, cookie *Cookie)

w 表示需要写入的 response,cookie 是一个 struct,让我们来看一下 cookie 对象是怎么样的

// A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an
// HTTP response or the Cookie header of an HTTP request.
//
// See https://tools.ietf.org/html/rfc6265 for details.
type Cookie struct {
	Name  string
	Value string

	Path       string    // optional
	Domain     string    // optional
	Expires    time.Time // optional
	RawExpires string    // for reading cookies only

	// 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
	SameSite SameSite
	Raw      string
	Unparsed []string // Raw text of unparsed attribute-value pairs
}

我们来看一个例子,如何设置 cookie

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

Go 读取 cookie

上面的例子演示了如何设置 cookie 数据,我们这里来演示一下如何读取 cookie

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

还有另外一种读取方式

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

可以看到通过 request 获取 cookie 非常方便。

如何发送这个 session 的唯一标识

考虑到 HTTP 协议的定义,数据无非可以放到请求行、头域或 Body 里,所以一般来说会有两种常用的方式:cookie 和 URL 重写。

  1. Cookie 服务端通过设置 Set-cookie 头就可以将 session 的标识符传送到客户端,而客户端此后的每一次请求都会带上这个标识符,另外一般包含 session 信息的 cookie 会将失效时间设置为 0 (会话 cookie),即浏览器进程有效时间。至于浏览器怎么处理这个 0,每个浏览器都有自己的方案,但差别都不会太大 (一般体现在新建浏览器窗口的时候)

  2. URL 重写 所谓 URL 重写,就是在返回给用户的页面里的所有的 URL 后面追加 session 标识符,这样用户在收到响应之后,无论点击响应页面里的哪个链接或提交表单,都会自动带上 session 标识符,从而就实现了会话的保持。虽然这种做法比较麻烦,但是,如果客户端禁用了 cookie 的话,此种方案将会是首选。

Go 实现 session 管理

通过上面 session 创建过程的讲解,读者应该对 session 有了一个大体的认识,但是具体到动态页面技术里面,又是怎么实现 session 的呢?下面我们将结合 session 的生命周期(lifecycle),来实现 go 语言版本的 session 管理。

session 管理设计

  • 全局 session 管理器
  • 保证 sessionid 的全局唯一性
  • 为每个客户关联一个 session
  • session 的存储 (可以存储到内存、文件、数据库等)
  • session 过期处理

session 劫持防范

cookieonly 和 token

通过上面 session 劫持的简单演示可以了解到 session 一旦被其他人劫持,就非常危险,劫持者可以假装成被劫持者进行很多非法操作。那么如何有效的防止 session 劫持呢?

其中一个解决方案就是 sessionID 的值只允许 cookie 设置,而不是通过 URL 重置方式设置,同时设置 cookiehttponlytrue, 这个属性是设置是否可通过客户端脚本访问这个设置的 cookie,第一这个可以防止这个 cookieXSS 读取从而引起 session 劫持,第二 cookie 设置不会像 URL 重置方式那么容易获取 sessionID

小结

如上文所述,session 和 cookie 的目的相同,都是为了克服 http 协议无状态的缺陷,但完成的方法不同。session 通过 cookie,在客户端保存 session id,而将用户的其他会话消息保存在服务端的 session 对象中,与此相对的,cookie 需要将所有信息都保存在客户端。因此 cookie 存在着一定的安全隐患,例如本地 cookie 中保存的用户名密码被破译,或 cookie 被其他网站收集(例如:1. appA 主动设置域 B cookie,让域 B cookie 获取;2. XSS,在 appA 上通过 javascript 获取 document.cookie,并传递给自己的 appB)。

通过上面的一些简单介绍我们了解了 cookie 和 session 的一些基础知识,知道他们之间的联系和区别,做 web 开发之前,有必要将一些必要知识了解清楚,才不会在用到时捉襟见肘,或是在调 bug 时如无头苍蝇乱转。