一. 会话控制
1.1 介绍
- HTTP是无状态的协议,不会记录用户的任何信息,服务器也不能记录浏览器的访问状态,也就是说服务器不能区分两次请求是否是同一个客户端发出的。
- Cookie是解决HTTP协议无状态的方案之一
- Cookie实际就是服务器保存在浏览器上的一段信息。浏览器有了Cookie之后,每次向服务器发送请求时,都会同时将该信息发送给服务器,服务器收到请求后,就可以根据该信息处理请求。
- Cookie由服务器创建,并发送给浏览器,最终由浏览器保存。
1.2 Cookie用途
- 服务器发送Cookie给客户端,客户端发送请求时携带Cookie
1.3 Cookie使用
- gin.Context的SetCookie方法可以用来设置Cookie
- SetCookie将Set-Cookie表头添加到ResponseWrite的标头中。提供的cookie必须有一个有效的名称,无效的cookie可能会被无提示删除。
// SetCookie adds a Set-Cookie header to the ResponseWriter's headers.
// The provided cookie must have a valid Name. Invalid cookies may be
// silently dropped.
func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) {if path == "" {path = "/"}http.SetCookie(c.Writer, &http.Cookie{Name: name,Value: url.QueryEscape(value),MaxAge: maxAge,Path: path,Domain: domain,SameSite: c.sameSite,Secure: secure,HttpOnly: httpOnly,})
}
- 参数
- name:cookie的key
- value:cookie的value
- maxAge:过期时间,如果只想设置Cookie的保存路径而不想设置存活时间,可以在第三个参数中传nil
- path:cookie的路径
- domain:cookie路径作用域,本地调试配置成localhost,正式上线配成域名
- secure:为true时,cookie在HTTP中无效,在HTTPS中有效
- httpOnly:是微软对 COOKIE 做的扩展。如果在 COOKIE 中设置了 “httpOnly” 属性,则通过程序(JS 脚本、 applet 等)将无法读取到 COOKIE 信息,防止 XSS 攻击产生。
- 获取Cookie,通过gin.Context的Cookie方法
package mainimport ("fmt""github.com/gin-gonic/gin"
)func main() {r := gin.Default()r.GET("cookie", func(c *gin.Context) {//获取客户端是否携带cookiecookie, err := c.Cookie("key_cookie")if err != nil {cookie = "NoCookie"//给客户端设置cookie//maxAge 单位为秒c.SetCookie("key_cookie", "val_cookie", 60, "/", "localhost", false, true)}fmt.Println("cookie: ", cookie)})r.Run()
}
1.4 Cookie练习
- 模拟实现权限验证中间件
- 两个路由login和home
- login用于设置cookie
- home处理请求返回响应
- 在请求home前,先跑中间件代码,检验是否存在cookie
package mainimport ("net/http""github.com/gin-gonic/gin"
)func MiddleWare() gin.HandlerFunc {return func(c *gin.Context) {//获取cookiecookie, err := c.Cookie("key_cookie")if err != nil {c.JSON(http.StatusUnauthorized, gin.H{"status": "fail", "err": err.Error()})//验证不通过,不在调用后续函数处理c.Abort()return}if cookie != "val_cookie" {c.JSON(http.StatusBadRequest, gin.H{"status": "fail", "err": "err cookie"})//验证不通过,不在调用后续函数处理c.Abort()return}//执行路由处理代码c.Next()}
}func main() {r := gin.Default()r.GET("/login", func(c *gin.Context) {//设置cookiec.SetCookie("key_cookie", "val_cookie", 60, "/", "127.0.0.1", false, true)c.String(http.StatusOK, "Login success")})r.GET("/home", MiddleWare(), func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"data": "home"})})r.Run()
}
演示:
1.5 Cookie缺点
- 不安全,明文发送
- 增加带宽消耗
- 可以被禁用
- cookie有上限
1.6 Session
在web开发中,大家一定会使用到session。在go框架中并没有集成session管理的中间件。要想使用session功能。可以使用gorilla/sessions这个包。
1.6.1 什么是session
session就是用来在服务端存储相关数据的,以便在同一个用户多次请求之间保存用户的状态,比如登录状态。因为HTTP协议是无状态的,不记录用户的信息,使用cookie的缺点不安全,明文发送,而session通过使用标识,也就是所谓的sessionid来解决cookie的不安全的缺点。
该sessionid有服务器生成,并存储在客户端的cookie或者url中。当客户端再次发送请求时,会携带该标识,服务端可以通过该标识查找到存在服务器上的相关数据。
原理如下:
1.6.2 gorilla/sessions包
gorilla/sessions包提供了将session数据存储与cookie和文件中的功能。同时还支持自定义后端存储,比如将session数据存储与redis,mysql等等。
使用下面命令安装:
go get github.com/gorilla/sessions
1.6.3 基本使用
该包的使用可以分为五步:定义存储session的变量,程序启动时实例化具体的session存储类型,读取或存储数据到session,持久化session。
package mainimport ("log""net/http""github.com/gin-gonic/gin""github.com/gorilla/sessions"
)// 第一步:定义全局的存储session数据变量
var sessionStore sessions.Storefunc main() {r := gin.Default()//第二步:程序启动后,指定具体的存储数据类型:redis,mysql或文件//这里指定的是文件sessionStore = sessions.NewFilesystemStore(".", []byte("Hello"))r.GET("/save", func(c *gin.Context) {//第三步:在具体的handler中获取session//Get() 总是返回一个sessionsession, err := sessionStore.Get(c.Request, "sessionid")if err != nil {log.Println(err.Error())}//第四步:从session中存储或取出数据session.Values["userid"] = "123123"//第五步保存session数据,本质上是将内存中的数据持久化到存储介质中//本例是持久化到文件中session.Save(c.Request, c.Writer)c.JSON(http.StatusOK, gin.H{"status": "ok",})})r.GET("/get", func(c *gin.Context) {session, err := sessionStore.Get(c.Request, "sessionid")if err != nil {c.JSON(http.StatusInternalServerError, gin.H{"status": "false","err": err.Error(),})}userid := session.Values["userid"]c.JSON(http.StatusOK, gin.H{"message": userid,"status": "ok",})})r.Run()
}
在该示例中,第一步的sessions.Store本质是一个接口类型,只要实现了该接口,就可以存储session数据。在第二步中就指定了具体存储类型:文件类型。返回的session的类型为*sessions.FilesystemStore,实现了session.Store接口。
在第三步获取session时,Store.Get有两个参数,一个是请求参数request,一个是session-name,存储于cookie或url中,当然也可以是在Header中。服务端从request中通过该参数名获取session-id,在通过session-id从后端存储中(文件,redis或mysql等)获取对应的数据,则读取出来并解析到session对象中,否则就初始化一个新的session对象。
第五步本质上是持久化。因为在第四步只是将数据保存到内存中,需要调用Save才能持久化到对应的存储介质。
演示:
目录结构:
客户端:
服务器多出来的session文件内容是一个加密字符串。
1.6.4 实现原理
session的存储本质上就是在服务端给每一个用户存储一行记录。服务端给每个用户分配一个唯一的session-id,以session-id为主键,存储对应的值。如果存储在mysql中,sessioin-id就是主键;如果存储在redis中,session-id就是key;如果存储在文件中,session-id就是对应的文件名,文件内容就是存储的session数据。
cookie和session原理:
我们拿登录账号举例:
我们知道http协议是无状态的,并不会记录用户的信息。但是我们实际生活中使用网页需要登录时,一般我们登录了一次,之后并不会需要再登录了。
这是因为http本身是无状态的,但是如果访问某资源频繁需要登录,会导致用户的体验不好。
cookie原理:
于是当浏览器访问某资源需要登录时,客户端将name和passwd发送个服务器,服务器与注册时保存的name和passwd对比,成功后响应,将name和passwd放在报头的set-cookie中发送给客户端,客户端将其保存到cookie中,下次访问时,将cookie信息发送给服务器,就不需要登录了。
cookie本质是一个浏览器的文件,里面保存用户的一些信息。
文件保存分为内存级(关闭浏览器进程就空间释放了),硬盘级文件(长时间保存在硬盘中,关闭进程并不会释放)。
但是:直接发送cookie,cookie保存的name和passwd并不安全,中间人可以直接获得用户的name和passward。
session原理:
在本地保存用户的信息,发送的时候不安全。于是,在客户端登录时,将name和passwd发送给服务器,服务器形成一个文件session,里面有一个标识sid,全互联网唯一,标识的是用户的信息,响应时将sid放到set-cookie里发送给客户端,客户端将sid保存到cookie中,下一次访问带有的cookie中保存的是sid,服务器只要拿着sid比较即可。
这样用户信息保存到了服务器,相比较只使用cookie安全性会高一点。