Cookie是指网站为了辨别用户身份而储存于客户端的数据,由网景公司的前雇员卢·蒙特利在1993年3月发明。最初定义于RFC 2109, 以及后续的规范 RFC 2965、RFC 6265。
服务器可以设置或读取Cookies中包含信息,借此维护用户跟服务器会话中的状态,并且可以基于Cookie实现Session,用来在服务器端存储用户的数据。
现在,几乎所有的商业网站都会使用Cookie技术用来标示浏览的用户,比如电子商务中的购物车、广告追踪系统等,并且涉及到一系列的安全问题和隐私问题。
Go的标准库中提供了Cookie的操作,并且第三方的库提供了Session的实现,所以在使用Go开发web应用中,我们可以很方便的实现session的管理,但是也有一些安全方面的设置需要注意。
本文介绍了使用Go语言开发web应用的时候,服务器端Cookie和Session的使用。
Cookie
千言不如一例, 首先我们看一下下面这个例子。这个例子提供了设置Cookie,读取Cookie和删除Cookie的示例。
|
|
主要是围绕http.Cookie
这个struct的一些方法。
- 读取:
http.Request
的Cookie(name string) (*Cookie, error)
和Cookies() []*Cookie
方法 ,http.Response
的Cookies() []*Cookie
方法。 - 写入:
http.Request
的AddCookie(c *Cookie)
,SetCookie(w ResponseWriter, cookie *Cookie)
函数
从服务器端来看, 我们从http.Request
中读取客户端传入的Cookie, 或者把Cookie写入到http.Response
中。
从客户端来看,我们需要把Cookie设置到http.Request
传给服务器,或者从http.Response
中读取Cookie。
所以你会看到Request
和Response
中都有读取和设置Cookie的方法,只是针对不同的场景而已。
查看下面的Cookie的定义,你也会看到有些字段在读取的时候是空值,这是因为在这个场景下,是没有这个值的,比如服务器端读取Cookie值时,浏览器并不会把Max-Age的传给服务器,所以在服务器端读取这个Cookie指的时候MaxAge值是0。
|
|
这几个字段要熟悉:
- Name/Value: Cookie的名称和指,cookie中最基本的数据
- Path: 可以将Cookie限定在某个路径下,只有这个路径和它的子路径才可以访问这个Cookie, 比如Path =
examle.com/abc/
的Cookie,只有examle.com/abc/
和子路径比如examle.com/abc/def
才能访问。默认为当前路径。 Domain: 只关联的web服务器的域名, 比如
example.com
。如果你设置的Cookie的domain为a.example.com
,那么访问b.example.com
的时候是不能访问这个Cookie的,如果想访问,那么请将domain设置它们共同的域example.com
。 你能在访问example.com
的时候设置domain为baidu.com
吗?不行,这是出于安全的限制。 默认是当前的域名。Expires: 为过期时间,Cookie超过这个时间点就会被删除了。
RawExpires: Expires字符串表示, 格式为
Wdy, DD Mon YYYY HH:MM:SS GMT
或者Wdy, DD Mon YY HH:MM:SS GMT
,在读取Cookie时候会被设置MaxAge: 最大存活时间,单位是秒,-1为删除这个Cookie, 0是不设置Max-Age, 正数为存活的秒数。你可以查看这个测试页了解不同的设置的效果。
如果同时设置了Expires
和MaxAge
,以Max-Age
为准。
Secure: 设置 Cookie 只在确保安全的请求中才会发送。当请求是 HTTPS 或者其他安全协议时,包含 secure 选项的 Cookie 才能被保存到浏览器或者发送至服务器。
HttpOnly: 这个选项用来设置 Cookie 是否能通过 js 去访问。强烈建议设置这个值为true,否则容易被XSS等攻击。你可以把上面的例子这个字段注释掉,访问首页的时候点击连接为显示当前页面的Cookie的值,这只是用来测试,要是有一段javascript把你的cookie传到第三方网站危险就大了。
SameSite: 2016年Chrome中加入的一个新属性,避免在跨域(XSRF)访问的时候把Cookie传给第三方网站。
Cookie的缺陷
Cookie也有一些天生的缺陷。
- Cookie会被附加在每个HTTP请求中,所以无形中增加了流量。
- 由于在HTTP请求中的Cookie是明文传递的,很容易遭受到中间人的攻击,所以安全性成问题,除非用HTTPS。
- Cookie的大小限制在4KB左右,对于复杂的存储需求来说是不够用的。
- 不同的浏览器Cookie不是共享的,你在Chrome中登录了一个网站,使用Firefox还需要再次登录,因为这两个浏览器的Cookie不共享。
- 同一台机器同一个浏览器会共享同一个Cookie池,A用完浏览器后,如果不清理Cookie, B使用的时候会得到A的Cookie。
- Cookie存在本地是明文访问的,其他用户如果能访问的这个Cookie,就能看到这个Cookie的内容
- 容易遭受XSS跨站访问
- 第三方脚本追踪。网站中嵌入第三方的代码,就容易被第三方公司利用,在一些互联网巨头和广告公司中经常会使用。你在A网站浏览一些商品,在浏览B网站的时候,B网站的广告会给你推送这些类似商品的信息,这是因为A和B都嵌入了同一个第三方公司的代码,通过Cookie能追踪到你的浏览记录。
- Cookie投毒攻击,例如在一个购物网站的Cookie中包含了顾客应付的款项,攻击者将该值改小,达到少付款的目的。
所以在使用Cookie的时候,需要一些额外的措施,避免收到攻击,下面是一些推荐设置:
- 设置合理的domain和path
- 设置合适的MaxAge, 不使用时或者推出时设置为-1
- 设置HttpOnly为true
- 设置SameSite
- 采用https, 设置Secure为true
- cookie不存储私密的东西,名称不设置直观易读的名称
- cookie进行加盐和加密
- 不设置太大的Cookie
- 设置到安全较高的操作时,服务器端对cookie和客户端ID(浏览器属性、操作系统、客户端IP)进行验证,避免被人窃取cookie
加密Cookie
涉及到私密的数据的时候,可以采取服务器加密的方式,在服务器先进行加密,再设置Cookie。你可以使用securecookie实现这个功能。
|
|
hashKey
用来生成Cookie值的摘要(hmac),可以验证数据是否被篡改,blockKey
是可选的,可以用来加密cookie的值。
cookie值可以是任意的对象,默认使用gob进行序列化,当然也可以配置使用json格式。
使用起来很方便,只是多一步Encode
/Decode
的过程。
它还提供了另一个好处,就是可以在服务器端验证Cookie的设置日期,在服务器端也进行MaxAge的校验。
Session
Cookie将数据存放在客户端,并且还有4k的大小的限制,为了更好的和用户进行交互,很多编程语言的开发框架提供了session的功能。
Session
还是基于cookie实现的(当然在禁用cookie的情况下可以在url后加后缀的方式曲折的实现)。一个session对应一个sessionid, 可以将这个sessionid作为cookie设置到客户端,服务器端建立一个 sessionid <---> session
的对应结构。 浏览器将sessionid发送给客户端的时候,服务器根据这个id得到session对象,就可以存取这个sessoin的内容。
可以将session放在内容中,也可以放在中心服务器如memcached、redis、mysql中,设置可以在web服务器中同步,这样可以实现有状态的session负载均衡。
sessionid要随机化,否则如果被人猜中的话,可以通过伪造session id冒充用户。
sessionid在cookie中的名称,不同的编程语言/web框架各不相同。
比如php使用PHPSESSID, java使用JSESSIONID, ColdFusion使用CFID & CFTOKEN, asp.net使用ASP.NET_SessionId, 通过cookie中的sessionid的名称,我们大致能推断出服务器所使用的编程语言。如果你不想暴露服务器的技术栈,你可以使用通用的名称,比如id
。
Go中有多个第三方的session实现,最常用的是gorilla/sessions, 它可以用在其他的go的web框架中,并且有一二十个不同存储的实现,可以实现分布式的session。
使用起来也很方便:
|
|
如果不使用。2018年12月9日 gorilla/sessions 的提交12bd476使用标准库context替换了它自己的标准库,不再有内存泄漏的问题了。gorilla/mux
框架,你需要context.ClearHandler
包装你的handler,否则会出现内存泄漏
beego中也实现了一个独立的session模块, 最近几天, fasthttp作者也实现了一个session库,当然秉承他的理念,性能是第一位的, 有兴趣的同学也可以关注下fasthttp/session。
JWT
对于访问认证来说,为每个客户端提供一个session对象,这对于用户访问巨大的网站来说,这是相当奢侈的。那么能不能提供一种机制,把用户的访问权限放在客户端,但是又能保证客户端的数据不被篡改?
类似securecookie机制,目前正在流行一种JWT的认证方式。
你可以搜索一些相关的介绍,比如什么是 JWT -- JSON WEB TOKEN、JSON Web Token 入门教程。
Go也有jwt的库可以使用, 比较有名的是jwt-go。
参考文档
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies
- https://www.sohamkamani.com/blog/2017/01/08/web-security-session-cookies/
- https://stackoverflow.com/questions/17834144/how-tomcat-handles-session-internally
- https://docs.spring.io/spring-session/docs/1.3.3.RELEASE/reference/html5/guides/custom-cookie.html#custom-cookie-spring-configuration
- https://stackoverflow.com/questions/595872/under-what-conditions-is-a-jsessionid-created
- https://www.jianshu.com/p/e8736aa3be2b
- https://www.cnblogs.com/doit8791/p/5926575.html
- https://harttle.land/2015/08/10/cookie-session.html
- https://mrcoles.com/blog/cookies-max-age-vs-expires/
- https://github.com/boj/redistore
- https://github.com/gorilla/sessions
- https://github.com/gorilla/securecookie
- https://github.com/fasthttp/session
- https://www.sohamkamani.com/blog/2018/03/25/golang-session-authentication/
- https://www.calhoun.io/securing-cookies-in-go/
- https://medium.com/@sherryhsu/session-vs-token-based-authentication-11a6c5ac45e4
- https://www.owasp.org/index.php/Session_Management_Cheat_Sheet
- https://en.wikipedia.org/wiki/HTTP_cookie#Cookie_theft_and_session_hijacking