Go web 开发中的cookie和session

栏目: Go · 发布时间: 6年前

内容简介:Cookie是指网站为了辨别用户身份而储存于客户端的数据,由网景公司的前雇员卢·蒙特利在1993年3月发明。最初定义于服务器可以设置或读取Cookies中包含信息,借此维护用户跟服务器会话中的状态,并且可以基于Cookie实现Session,用来在服务器端存储用户的数据。现在,几乎所有的商业网站都会使用Cookie技术用来标示浏览的用户,比如电子商务中的购物车、广告追踪系统等,并且涉及到一系列的安全问题和隐私问题。

Cookie是指网站为了辨别用户身份而储存于客户端的数据,由网景公司的前雇员卢·蒙特利在1993年3月发明。最初定义于 RFC 2109 , 以及后续的规范 RFC 2965RFC 6265

服务器可以设置或读取Cookies中包含信息,借此维护用户跟服务器会话中的状态,并且可以基于Cookie实现Session,用来在服务器端存储用户的数据。

现在,几乎所有的商业网站都会使用Cookie技术用来标示浏览的用户,比如电子商务中的购物车、广告追踪系统等,并且涉及到一系列的安全问题和隐私问题。

Go的标准库中提供了Cookie的操作,并且第三方的库提供了Session的实现,所以在使用 Go 开发web应用中,我们可以很方便的实现session的管理,但是也有一些安全方面的设置需要注意。

本文介绍了使用Go语言开发web应用的时候,服务器端Cookie和Session的使用。

Cookie

千言不如一例, 首先我们看一下下面这个例子。这个例子提供了设置Cookie,读取Cookie和删除Cookie的示例。

package main

import (
	"encoding/json"
	"flag"
	"net/http"
)

var (
	addr = flag.String("addr", ":8080", "server address")
)

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/", index)
	mux.HandleFunc("/get", getCookie)
	mux.HandleFunc("/delete", deleteCookie)
	mux.HandleFunc("/set", setCookie)
	http.ListenAndServe(*addr, mux)
}

func index(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte(`<a href="#" onclick="alert(document.cookie)">Click here!</a>`))
}

func getCookie(w http.ResponseWriter, r *http.Request) {
	c, err := r.Cookie("this_is_a_test_cookie")
	if err != nil {
		w.Write([]byte("读取cookie失败: " + err.Error()))
	} else {
		data, _ := json.MarshalIndent(c, "", "\t")
		w.Write([]byte("读取的cookie值: \n" + string(data)))
	}
}

func deleteCookie(w http.ResponseWriter, r *http.Request) {
	c := http.Cookie{
		Name:   "this_is_a_test_cookie",
		MaxAge: -1}
	http.SetCookie(w, &c)

	w.Write([]byte("cookie已被删除"))
}

func setCookie(w http.ResponseWriter, r *http.Request) {
	c := http.Cookie{
		Name:     "this_is_a_test_cookie",
		Value:    "true",
		HttpOnly: true,
		//Secure:   true,
		MaxAge: 300}
	http.SetCookie(w, &c)

	w.Write([]byte("cookie已创建\n"))
}

主要是围绕 http.Cookie 这个struct的一些方法。

  • 读取: http.RequestCookie(name string) (*Cookie, error)Cookies() []*Cookie 方法 , http.ResponseCookies() []*Cookie 方法。
  • 写入: http.RequestAddCookie(c *Cookie) , SetCookie(w ResponseWriter, cookie *Cookie) 函数

从服务器端来看, 我们从 http.Request 中读取客户端传入的Cookie, 或者把Cookie写入到 http.Response 中。

从客户端来看,我们需要把Cookie设置到 http.Request 传给服务器,或者从 http.Response 中读取Cookie。

所以你会看到 RequestResponse 中都有读取和设置Cookie的方法,只是针对不同的场景而已。

查看下面的Cookie的定义,你也会看到有些字段在读取的时候是空值,这是因为在这个场景下,是没有这个值的,比如服务器端读取Cookie值时,浏览器并不会把Max-Age的传给服务器,所以在服务器端读取这个Cookie指的时候MaxAge值是0。

type Cookie struct {
        Name  string
        Value string

        Path       string
        Domain     string
        Expires    time.Time 
        RawExpires string 
        MaxAge   int
        Secure   bool
        HttpOnly bool
        SameSite SameSite
        Raw      string
        Unparsed []string
}

这几个字段要熟悉:

  • 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, 正数为存活的秒数。你可以查看这个 测试页 了解不同的设置的效果。

如果同时设置了 ExpiresMaxAge ,以 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 实现这个功能。

// Hash keys should be at least 32 bytes long
var hashKey = []byte("very-secret")
// Block keys should be 16 bytes (AES-128) or 32 bytes (AES-256) long.
// Shorter keys may weaken the encryption used.
var blockKey = []byte("a-lot-secret")
var s = securecookie.New(hashKey, blockKey)

func SetCookieHandler(w http.ResponseWriter, r *http.Request) {
	value := map[string]string{
		"foo": "bar",
	}
	if encoded, err := s.Encode("cookie-name", value); err == nil {
		cookie := &http.Cookie{
			Name:  "cookie-name",
			Value: encoded,
			Path:  "/",
			Secure: true,
			HttpOnly: true,
		}
		http.SetCookie(w, cookie)
	}
}

func ReadCookieHandler(w http.ResponseWriter, r *http.Request) {
	if cookie, err := r.Cookie("cookie-name"); err == nil {
		value := make(map[string]string)
		if err = s2.Decode("cookie-name", cookie.Value, &value); err == nil {
			fmt.Fprintf(w, "The value of foo is %q", value["foo"])
		}
	}
}

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放在内容中,也可以放在中心服务器如 memcachedredismysql 中,设置可以在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。

使用起来也很方便:

import (
	"net/http"
	"github.com/gorilla/sessions"
)

var store = sessions.NewCookieStore(os.Getenv("SESSION_KEY"))

func MyHandler(w http.ResponseWriter, r *http.Request) {
	// 得到一个session
	session, _ := store.Get(r, "session-name")
	// 设置session的一些值
	session.Values["foo"] = "bar"
	session.Values[42] =43
       // 在返回之前保存它
	session.Save(r, w)
   }

如果不使用 gorilla/mux 框架,你需要 context.ClearHandler 包装你的handler,否则会出现内存泄漏。

beego中也实现了一个独立的session模块, 最近几天, fasthttp作者也实现了一个session库,当然秉承他的理念,性能是第一位的, 有兴趣的同学也可以关注下 fasthttp/session

JWT

对于访问认证来说,为每个客户端提供一个session对象,这对于用户访问巨大的网站来说,这是相当奢侈的。那么能不能提供一种机制,把用户的访问权限放在客户端,但是又能保证客户端的数据不被篡改?

类似securecookie机制,目前正在流行一种JWT的认证方式。

你可以搜索一些相关的介绍,比如 什么是 JWT -- JSON WEB TOKENJSON Web Token 入门教程

Go也有jwt的库可以使用, 比较有名的是 jwt-go

参考文档

  1. https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies
  2. https://www.sohamkamani.com/blog/2017/01/08/web-security-session-cookies/
  3. https://stackoverflow.com/questions/17834144/how-tomcat-handles-session-internally
  4. https://docs.spring.io/spring-session/docs/1.3.3.RELEASE/reference/html5/guides/custom-cookie.html#custom-cookie-spring-configuration
  5. https://stackoverflow.com/questions/595872/under-what-conditions-is-a-jsessionid-created
  6. https://www.jianshu.com/p/e8736aa3be2b
  7. https://www.cnblogs.com/doit8791/p/5926575.html
  8. https://harttle.land/2015/08/10/cookie-session.html
  9. https://mrcoles.com/blog/cookies-max-age-vs-expires/
  10. https://github.com/boj/redistore
  11. https://github.com/gorilla/sessions
  12. https://github.com/gorilla/securecookie
  13. https://github.com/fasthttp/session
  14. https://www.sohamkamani.com/blog/2018/03/25/golang-session-authentication/
  15. https://www.calhoun.io/securing-cookies-in-go/
  16. https://medium.com/@sherryhsu/session-vs-token-based-authentication-11a6c5ac45e4
  17. https://www.owasp.org/index.php/Session_Management_Cheat_Sheet
  18. https://en.wikipedia.org/wiki/HTTP_cookie#Cookie_theft_and_session_hijacking

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

腾讯方法

腾讯方法

潘东燕、王晓明 / 机械工业出版社 / 2014-12-11 / 39.00

这是国内第一本深度讲述腾讯产品研发与团队转型的书。本书介绍了腾讯三个不同生命周期的产品的开发过程,包括如何踏足新领域开发新产品;如何救活一个即将半路夭折的产品;如何让一个老产品持续盈利。本书呈现了互联网产品开发时会遇到普遍问题和解决方法,涉及大企业如何内部创业,并迅速组建新的项目团队;如何实现跨部门的合作;在面临新团队和紧急开发任务时如何提高团队沟通效率;在产品研发方面,如何定位产品、如何敏捷开发......一起来看看 《腾讯方法》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器