go cookie学习

cookie的概念

cookie是由服务器发给客户端的特殊信息,而这些信息以文本文件的方式存放在客户端,然后客户端每次向服务器发送请求的时候都会带上这些特殊的信息,用于服务器记录客户端的状态。

cookie的作用

解决每个http请求相互独立问题。这是因为HTTP 1.x是无状态的协议,它的每个请求都是完全独立的。当然解决这个问题也可以使用sessio,这里不说session,只说cookie。

cookie解决的过程

携带cookie的HTTP响应体一种方式为:

Content-Length: 13
Content-Type: text/plain; charset=utf-8
Date: Sat, 21 Aug 2021 14:18:41 GMT
Set-Cookie: myname1=mykey1

浏览器收到服务器返回数据,发现请求头中有一个:Set-Cookie,然后它就把这个Cookie保存起来,下次浏览器再请求服务器的时候,会把Cookie也放在请求头中传给服务器:

Host: localhost:8080
Cookie: myname1=mykey1

cookie的属性

我们看下面的图。发现cookie拥有的属性有:Name、Value、Domain、Path、Expires/Max-Age、Size、HTTP、Secure。

属性介绍

1. Name&Value

Name表示Cookie的名称,服务器就是通过name属性来获取某个Cookie值。

Value表示Cookie 的值,大多数情况下服务器会把这个value当作一个key去缓存中查询保存的数据。

2.Domain&Path

Domain表示可以访问此cookie的域名。比如这里是localhost域,因此其他非localhost的域,在发起请求的时候,不能携带这个cookie。如果想要跨域携带,可以:如域A为t1.test.com,域B为t2.test.com,那么在域A生产一个令域A和域B都能访问的cookie就要将该cookie的domain设置为.test.com;

Path表示可以访问此cookie的页面路径。 比如path=/test,那么只有/test(包括/test子目录,比如:/test/a)路径下的页面可以读取此cookie。

3.Expires/Max-Age

Expires/Max-Age表示此cookie超时时间。若设置其值为一个时间,那么当到达此时间后,此cookie失效。不设置的话默认值是Session,意思是cookie会和session一起失效。当浏览器关闭(不是浏览器标签页,而是整个浏览器) 后,此cookie失效。

4.Size

Size表示Cookie的name+value的字符数,比如又一个Cookie:id=666,那么Size=2+3=5 。

5.HTTP

HTTP表示cookie的httponly属性。若此属性为true,则只有在http请求头中会带有此cookie的信息,而不能通过document.cookie来访问此cookie。

设计该特征意在提供一个安全措施来帮助阻止通过Javascript发起的跨站脚本攻击(XSS)窃取cookie的行为

6.Secure

Secure表示是否只能通过https来传递此条cookie。不像其它选项,该选项只是一个标记并且没有其它的值。

golang中cookie的属性

  • go cookie struct

    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
    }
    
    • 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
    • Expires: 指定cookie到什么时候过期时间,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, 正数为存活的秒数。
    • Secure: 设置 Cookie 只在确保安全的请求中才会发送。当请求是 HTTPS 或者其他安全协议时,包含 secure 选项的 Cookie 才能被保存到浏览器或者发送至服务器。
    • HttpOnly: 这个选项用来设置 Cookie 是否能通过 js 去访问。强烈建议设置这个值为true,否则容易被XSS等攻击。你可以把上面的例子这个字段注释掉,访问首页的时候点击连接为显示当前页面的Cookie的值,这只是用来测试,要是有一段javascript把你的cookie传到第三方网站危险就大了
    • SameSite: 2016年Chrome中加入的一个新属性,避免在跨域(XSRF)访问的时候把Cookie传给第三方网站。

代码实现

  • 设置cookie

    package main
    import (
    	"fmt"
    	"net/http"
    )
    
    func main(){
    	fmt.Println("创建cookie")
    	http.HandleFunc("/setCookie1",setCookie1)
    	http.HandleFunc("/setCookie2",setCookie2)
    	http.HandleFunc("/sendData",sendData)
    	http.ListenAndServe(":8080",nil)
    }
    
    // 设置cookie值
    func setCookie1(w http.ResponseWriter,r *http.Request){
    	c:=http.Cookie{Name:"myname1",Value:"mykey1"}
    	http.SetCookie(w,&c)
    	fmt.Fprintln(w,"无名之辈")
    }
    // 设置cookie值
    func setCookie2(w http.ResponseWriter,r *http.Request){
    	c:=http.Cookie{Name:"myname2",Value:"mykey2"}
    	http.SetCookie(w,&c)
    	fmt.Fprintln(w,"无名之辈2")
    }
    
    func sendData(w http.ResponseWriter,r *http.Request){
    	fmt.Fprintln(w,"海贼王")
    }
    
    • 问题思考:

      • 服务端每次响应时是否要都要设置cookie的请求头?

        答:不用。首先,在第一次访问之后,服务端设置的cookie字段,发送出去之后,便可以在下一次访问需要有cookie的接口的时候,服务端不需要再设置cookie。只需要判断cookie的有效性就行

      • 同个域名下,不同的处理函数中,同时都设置cookie,会发生什么样的结果?

        • cookie会以逗号隔开连接在一起

        • 访问上面两个设置cookie接口,然后再访问sendData接口的cookie结果

  • 获取cookie

    • 有两个方法:cookies()获取cookie数组,cookie(cookieName)获取指定的名字的cookie的value
     package main
     
     import (
     	"fmt"
     	"net/http"
     )
     
     func main(){
     	fmt.Println("创建cookie")
     	http.HandleFunc("/setCookie1",setCookie1)
     	http.HandleFunc("/setCookie2",setCookie2)
     	http.HandleFunc("/sendData",sendData)
     	http.HandleFunc("/getCookie",getCookie)
     	http.ListenAndServe(":8080",nil)
     }
     
     
     func setCookie1(w http.ResponseWriter,r *http.Request){
     	c:=http.Cookie{Name:"myname1",Value:"mykey1"}
     	http.SetCookie(w,&c)
     	fmt.Fprintln(w,"无名之辈")
     }
     
     func setCookie2(w http.ResponseWriter,r *http.Request){
     	c:=http.Cookie{Name:"myname2",Value:"mykey2"}
     	http.SetCookie(w,&c)
     	fmt.Fprintln(w,"无名之辈2")
     }
     
     func sendData(w http.ResponseWriter,r *http.Request){
     	fmt.Fprintln(w,"海贼王")
     }
     // 获取cookie
     func getCookie(w http.ResponseWriter,r *http.Request){
     	var data string
     	for i:=0;i<len(r.Cookies());i++{
     		fmt.Println(r.Cookies()[i])
     		data = data + r.Cookies()[i].String()
     	}
     	c,_:=r.Cookie("myname2")
     	data = data + c.String()
     	fmt.Fprintln(w,data)
     }
    

cookie利用

  • 利用cookie发送一次性数据
package main

import (
	"fmt"
	"net/http"
)

func main(){
	fmt.Println("创建cookie")
	http.HandleFunc("/setCookie1",setCookie1)
	http.HandleFunc("/setCookie2",setCookie2)
	http.HandleFunc("/sendData",sendData)
	http.HandleFunc("/getCookie",getCookie)
	http.ListenAndServe(":8080",nil)
}


func setCookie1(w http.ResponseWriter,r *http.Request){
	c:=http.Cookie{Name:"myname1",Value:"mykey1"}
	http.SetCookie(w,&c)
	fmt.Fprintln(w,"无名之辈")
}

func setCookie2(w http.ResponseWriter,r *http.Request){
	c:=http.Cookie{Name:"myname2",Value:"mykey2"}
	http.SetCookie(w,&c)
	fmt.Fprintln(w,"无名之辈2")
}

func sendData(w http.ResponseWriter,r *http.Request){
	_,err1:= r.Cookie("myname1")
	_,err2:= r.Cookie("myname2")
	if err1!=nil && err2!=nil{
		fmt.Fprintln(w,"no message to show")
		return
	}else{
		// 将所有的cookie设置过期
		expireCookies := http.Cookie{
			Name:"myname1",
			MaxAge:-1,
		}
		expireCookies1 := http.Cookie{
			Name:"myname2",
			MaxAge:-1,
		}
		http.SetCookie(w,&expireCookies)
		http.SetCookie(w,&expireCookies1)
		fmt.Fprintln(w,"海贼王")
	}
}

func getCookie(w http.ResponseWriter,r *http.Request){
	var data string
	for i:=0;i<len(r.Cookies());i++{
		fmt.Println(r.Cookies()[i])
		data = data + r.Cookies()[i].String()
	}
	c,_:=r.Cookie("myname2")
	data = data + c.String()
	fmt.Fprintln(w,data)
}
  • 验证方式
    • 分别访问一次setCookie1和setCookie2接口,再访问sendData接口。
    • 再次访问sendData接口

cookie文件查找

cookie的保存,就是将cookie的值写入到客户端的cookie文件中。这里介绍怎么从chrome中去查看cookie.txt。

1、在chrome的输入框中输入:chrome://settings/siteData。

2、根据host找到cookie文件。

根据刚才找到的文件,打开就可以看到cookie拥有的属性了。如下:

注:这里的脚本可访问如果为是,则可以使用document.cookie()或者其他脚本获取到。如果不是,则只能通过HTTP传递获取。

cookie特点

优点

  • 不需要任何服务器资源,因为cookie是存在客户端并发送给服务端读取的
  • 可配置到期规则,控制cookie的生命周期,使之不会永远生效,偷盗者可能拿到的是一个过期的cookie

缺点

  • Cookie会被附加在每个HTTP请求中,所以无形中增加了流量。
  • 由于在HTTP请求中的Cookie是明文传递的,很容易遭受到中间人的攻击,所以安全性成问题,除非用HTTPS。
  • Cookie的大小限制在4KB左右,对于复杂的存储需求来说是不够用的。
  • 不同浏览器不能共享
  • 容易遭受XSS跨站访问
  • Cookie投毒攻击,例如在一个购物网站的Cookie中包含了顾客应付的款项,攻击者将该值改小,达到少付款的目的。

使用注意事项

  • 设置合理的domain和path
  • 设置合适的MaxAge, 不使用时或者推出时设置为-1
  • 设置HttpOnly为true
  • 设置SameSite。可以参考:https://www.ruanyifeng.com/blog/2019/09/cookie-samesite.html
  • 采用https, 设置Secure为true
  • 设置到安全较高的操作时,服务器端对cookie和客户端ID(浏览器属性、操作系统、客户端IP)进行验证,避免被人窃取cookie

参考文档

Go web 开发中的cookie和session

Cookie起源与发展

posted @ 2019-10-19 22:20  Myuniverse  阅读(170)  评论(0编辑  收藏  举报