目录

  • ​​1. 登录实现方案与实践​​
  • ​​1.1 cookie-介绍​​
  • ​​1.2 cookie-服务端操作​​
  • ​​1.3 cookie-登录校验​​
  • ​​1.4 session​​
  • ​​1.5 gin实现登录-session​​
  • ​​1.6 gin实现登录-模拟登录​​
  • ​​1.7 完善登录功能-对接数据库​​
  • ​​1.8 完善登录功能-重写登录中间件​​

1. 登录实现方案与实践

1.1 cookie-介绍

cookie是什么?

cookie是存储在浏览器上的一段字符串,最大是5KB,

所以说cookie是有大小限制的,为什么大小限制我们待会解释。

然后每一个域都可以有一个cookie,但cookie跨域不共享,这是属于浏览器的安全机制范畴。

cookie它会随着HTTP请求传递给服务端,

每一次HTTP请求cookie都会传到服务端。

因为我们的请求要求是尽量的请求的量要小一点,这样请求就会快一些。

如果请求的量很大的话,你的网速又不快的话,你的请求就会变得很慢。

所以说cook它不能很大,如果很大的话,每次请求都带来传给服务端,‍‍

这肯定是导致每次请求都非常慢,所以说cookie的大小限制是一定要有的。

每次HTTP请求cookie都会随着传递给服务端,‍‍

服务端肯定是可以修改cookie再返回给前端,这个肯定是可以操作的。

因为在web系统中服务端是万能的,服务端什么都可以做。

cookie的格式

k1=v1;k2=v2;k3=v3

分号分割。

数据格式是结构化的。

怎么使用cookie实现登录校验?cookie实现登录的原理是什么?cookie如何用于登录?

涉及到HTTP协议。

HTTP协议是前后端数据交互的协议。

1.2 cookie-服务端操作

设置cookie——怎么去增加cookie的内容。

代码:

package main

import (
"github.com/gin-gonic/gin"
)

func main() {

router := gin.Default()

router.GET("/read_cookie", func(c *gin.Context) {
// 设置cookie
c.SetCookie("gin_cookie","test",3600,"/","localhost",false,true)
router.Run(":3000")
}

结果:

从0到1,输入字符,避免眼高手低。

登录实现方案与实践_登录方案实践

登录实现方案与实践_用户信息_02

获取cookie

代码:

package main

import (
"fmt"
"github.com/gin-gonic/gin"
)

func main() {

router := gin.Default()

router.GET("/read_cookie", func(c *gin.Context) {
// 设置cookie
c.SetCookie("gin_cookie","test",3600,"/","localhost",false,true)

// 获取Cookie
cookie,err := c.Cookie("gin_cookie")
if err != nil{
cookie = "CookieNotSet"
}
fmt.Printf("Cookie value:%s \n",cookie)
})
router.Run(":3000")
}

截图:

登录实现方案与实践_用户信息_03

cookie的key是gin_cookie,value是test,即

登录实现方案与实践_登录方案实践_04

结构化cookie

登录实现方案与实践_登录方案实践_05

最终代码:

难点是将[]*http.Cookie类型转化为map对象,已解决,参考代码或其他问题的链接。

反思:设置cookie—获取cookie应该分门别类写接口,不然代码融合在一起会不优雅。

func main() {

router := gin.Default()

router.GET("/read_cookie", func(c *gin.Context) {

// 获取所有cookies
cookieStr := c.Request.Cookies()
// 获取所有的cookies:[gin_cookie=test gin_cookie_02=test02]
// []*http.Cookie
fmt.Printf("获取所有的cookies:%v\n%T\n",cookieStr,cookieStr)

// 结构化cookie
// 把 [gin_cookie=test gin_cookie_02=test02] ===> {"gin_cookie":"test","gin_cookie_02":"test02"}
// 方法:https://stackoom.com/question/4Wr8f
cookieObj := make(map[string]interface{})

// 对切片循环遍历元素
for _,v := range cookieStr {
// []*http.Cookie类型转化为map对象
cookieObj[v.Name] = v.Value
}

fmt.Printf("将[]*http.Cookie结构化成map对象:%s\n",cookieObj)

})
router.Run(":3000")
}

最终截图:

登录实现方案与实践_登录方案实践_06

后台输出:

登录实现方案与实践_登录方案实践_07

其他问题:

搜索 go gin cookie

​cookie/session​

​go-string-split​

问题描述:

您不能将 Cookie 类型( []*http.Cookie类型cannot convert rc (type []*http.Cookie) to type string为字符串类型( cannot convert rc (type []*http.Cookie) to type string )。

理想情况下,有什么替代方法或其他方法可以转换为字符串类型我仍然会返回类型字符串。我相对较新,所以在尝试其他方面有点困难。

理想情况下,它会像cookie=some_cookie_value一样作为字符串返回。

1.3 cookie-登录校验

请求登录接口,成功则设置cookie,如user=zhangsan。

反过来说失败就不设置cookie。

服务端可以设置cookie,可以获取cookie,可以结构化cookie。

你去请求登录接口的时候,或者请求登录路由的时候,我判断成功了,我就设置cookie,‍‍判断不成功我就不设置cookie。

你登录的时候我判断你成功了,我去设置cookie,把用户名zhangsan给你设置在cookie里面去,‍‍

如果你登录失败了,我就不设置cookie。

这个时候,如果是之前已经登录过,前端再次请求其他接口的时候,‍‍ta就会带着 cookie,

‍‍我们说过cookie会随着网络请求发送给服务端,前提是同域不是跨域。

所以说如果是之前这一步请求登录接口的时候成功了,设置了cookie像user=zhangsan这种格式,

他在请求其他的接口的时候就会带来cookie。

‍‍当然如果是他请求登录的接口失败了没有设置cookie,那么他在请用其他接口的时候,他就不会带着cookie。

服务端可以判断cookie有无,例如像有没有user=zhangsan来验证是不是登录。

我们可以简单的去描述一下这样一个场景。‍‍

比如说我现在是刚刚访问这个网站,我现在还没有cookie 像user=zhangsan这样的cookie,这个时候我要尝试去登录,

比如说我登录是成功的,成功之后服务端给我设置了cookie并且返回‍‍,前端就有了cookie 比如user=zhangsan,

然后我再请去其他接口的时候就会带着 cookie。‍‍

然后服务端再去判断cookie的有无,这个时候我是有cookie的对吧?

ta就会认为我已经登录了,这个时候用户校验就已经通过,‍‍然后ta就可以给我正常返回一些信息,

比如说前端再请求其他接口,比如说去创建一个留言对吧?或获取留言列表等等这些,‍‍或者获取我的留言列表,

这个时候服务端都需要判断cookie去判断有没有登录,因为你没有登录是不让你发表留言的,对吧?‍‍

万一你发表乱七八糟的信息,我知道谁发表了?‍‍

好,我们再反过来说,如果我现在是一个没有登录过的新用户,也没有cookie,‍‍我去请求登录接口,这个时候登录失败了,登录失败的时候肯定是服务端不给我设置cookie对吧?

我现在没有cookie,‍‍这样的话我再请求其他接口,比如说我去创建留言,就没有cookie可以带对吧?你没有cookie你带个什么劲?带不了。‍‍

带不了cookie你请求其他接口的时候,服务端判断你原来没有cookie,你没有像user=zhangsan这种cookie,‍‍

抱歉你没有登录,没有验证通过,所以说通过这么三步:

1)请求登录接口,成功则设置cookie,如user=zhangsan

2)前端再请求其他接口,就会带着上述cookie

3)服务端判断cookie有无,比如user=zhangsan,即可验证用户是否登录成功

我们可以用图来表示一下:

登录实现方案与实践_服务端_08

比如说一开始什么都没有,然后去登录,登录之后服务端去设置cookie,

然后比如说user=zhangsan,‍‍然后到了前端收到cookie之后,在每次请求其他的接口比如创建留言的接口时候,就可以带着 这个cookie 到服务端,‍‍

服务端通过判断这个cookie有没有,然后去判断用户有没有登录,或者说到底用户是谁,张三还是李四对吧?都在这里面写着。‍‍

这就是cookie怎么去用于登录校验。

1.4 session

session是存储用户信息的,session是怎么存储用户信息的?

涉及到数据库,中间件。

session是什么?

cookie不能暴露用户名。

通过前面的学习,我们已经知道了怎么用cookie校验登录信息,有点瑕疵就是cookie里面能看到用户名,也就是说cookie存放的用户信息它是明文的,这就是问题所在了。

那怎么解决这个问题呢?

解决方案:cookie存一个用户标识,如userId=123。

这样理解就对了。

cookie里面我们存一个用户标识就可以了,比如说userId=123,

标识可以是一个数字,可以随便是一个什么样的代号,‍‍反正就是存一个用户标识,我不会把用户的明文信息放在这儿来,我只是把用户的一个ID或者一个标识放在这儿来,

比如说没有意义的字符串或者是一个数字,‍‍放在这来就可以了。‍‍

这样的话是不是就不危险了?对吧。

我的标识可以给你,123可以给你,但是我的用户名手机号邮箱不会给你。

那么问题又来了,我如何确保这个标识是属于那个用户呢?对应关系是什么?

我们可以在cookie里面是放一个用户标识,不放用户名,但是我怎么才能知道根据这个用户标识对应到ta是谁呢?‍‍

这就是session的背景和产生的原因了。

cookie存储用户标识,然后用户信息存储到session中,然后它们去建一个对应关系,‍‍这也是cookie和session之间的一个关系,也是session里面存储用户的信息,cookie它是存储用户的标识,‍‍然后标识和用户信息做一个对应的关系。‍‍(字典key-value映射关系)

我们看图:

登录实现方案与实践_用户信息_09

左边这一块其实我们之前已经讲过,之前我们讲的时候的cookie是直接存用户名的对吧?然后我们现在改过来了,cookie里面存用户标识,存一个用户ID,‍‍然后怎么知道用户ID是谁?

我们在服务端就建一个session,这个session可以是个对象,也可以存在数据库里面,总之怎么都行啊,形式上没有规定,‍‍因为session它本身就是一个技术的解决方案,它并没有规定说你存到什么地方去,假如说它就是一个全局对象,‍‍这个时候我们可以在里面去建存很多东西,比如说我们用户案例是123,然后123为key然后以一个对象为value,它去存储起来用户信息,比如说​​user:zhangsan​​,

然后‍‍还可以扩展其他信息,比如说真实姓名,手机号,年龄,邮箱等等都可以放在这儿来。

所以说我们可以通过在后端保留一个session的集合,session的集合就是所有用户信息,‍‍然后 key 就是123 或者 456 这些用户ID,

然后每个用户登录完成之后,我们在cookie里面给他放个用户ID‍‍,

然后我们在其他接口里面,比如说创建留言的时候,前端传了cookie 的 userid=123,‍‍

然后服务端就拿123去session里面去找,能找到的那就是正常的用户,

然后获取用户名该怎么做就怎么做,‍‍找不到那就说明是你的用户用ID是一个假的,或者说是已经过期了,或者说已经没有用了,反正是不行,没有登录。‍‍

当然还有一种情况就是你这里面连userId=123都没有,那没有肯定是没有登录对吧? cookie都不存在。‍‍

好,所以说根据这个图我们就能看出 session 和 cookie 的一个关系。

然后我们可以简单用代码去展示一下这个过程,写博客就好好写,写点有用的真东西。

代码:

// 模拟session
// Go嵌套map类型

var USERINFO_DATA=map[string]interface{}{
"user":"zhangsan",
"age":18,
}
var SESSION_DATA=map[string]interface{}{
"123":USERINFO_DATA,
}
// invalid composite literal type: interface{}
// 参考链接:https://www.itbaoku.cn/post/809967.html

func main(){
fmt.Println(SESSION_DATA)
router := gin.Default()
router.GET("/set_cookie",func(c *gin.Context){
// 假如用户登录成功,服务端设置cookie(userId,不能泄露用户信息)
c.SetCookie("userId","123",3600,"/","localhost",false,true)
})
router.GET("/get_cookie",func(c *gin.Context){
// 其他接口获取cookie,返回两个值,if err != nil 判断
userId,err:= c.Cookie("userId")

if err != nil{
userId = "CookieNotSet"
}
fmt.Printf("userId value:%s\n",userId)
// 取出userId对应的信息
userInfo := SESSION_DATA[userId]
// 取出用户名
userName := userInfo.(map[string]interface{})["user"]
fmt.Println(userName)
// cookie session 联合
})
router.Run(":3000")
}

截图:

登录实现方案与实践_服务端_10

登录实现方案与实践_用户名_11

登录实现方案与实践_用户名_12

1.5 gin实现登录-session

安装gin框架的session:

打开终端输入:

​go get github.com/gin-contrib/sessions​

提示:在gin框架中,无论是Set一个session,还是Delete一个session,都要调用Save()方法进行保存。

如何学习使用 gin session?

善用搜索:

登录实现方案与实践_服务端_13

gin session 使用

最终代码:

func main(){
router := gin.Default()

// 创建基于cookie的存储引擎,secret111参数是用于加密的秘钥
store := cookie.NewStore([]byte("secret111111"))
// 设置session中间件,参数是mysession,指的是session的名字,也是cookie的名字
router.Use(sessions.Sessions("mysession",store))
// 测试session,记录访问次数
// session常用于登录,存储用于信息,通过cookie来对应,用户访问次数可以作为用户信息
// cookie登录成功之后设置的,用户尚未登录也可以设置cookie,用户信息我们可以自定义
// cookie:"id=123123" session:{"123123":{"viewcount":1}}
{
router.GET("/session_test",func(c *gin.Context){
// 初始化session对象
session := sessions.Default(c)
// 如果用户未访问
// session.Get读取session值 session是键值对格式的数据 所以通过key来查询数据
if session.Get("viewcount") == nil{
// 用户未访问
session.Set("viewcount",0)
// 调用save方法,写入/保存 session数据
session.Save()
}
// mismatched types interface{} and untyped int
// 参考链接:https://stackoverflow.com/questions/18041334/convert-interface-to-int
viewCount := session.Get("viewcount").(int)
// 用户已经访问过了
// 递增
viewCount++
// 不加以下2行代码不会累加的
// 将viewCount的值设置/存入到viewcount中
session.Set("viewcount",viewCount)
// 调用save方法,写入/保存 session数据,
/* Set设置参数---Get获取参数 我们通过Get拿到"viewcount"进行累加,还要通过Set存储到"viewcount"中,然后Save */
session.Save()
c.JSON(http.StatusOK,gin.H{
"title":"session_test",
"viewcount":viewCount,
})
})
}
router.Run(":3000")
}

截图:

登录实现方案与实践_用户名_14

其它问题:

问题描述:

go-convert-interface-to-int ,mismatched types interface{} and untyped int

解决方法:

使用类型断言。

​参考链接​

登录实现方案与实践_服务端_15

比如我这里的代码就是viewCount.(int)解决了问题​​go-convert-interface-to-int ,mismatched types interface{} and untyped int​​。

1.6 gin实现登录-模拟登录

代码:

// 模拟session
// Go嵌套map类型

var USERINFO_DATA=map[string]interface{}{
"username":"zhangsan",
"age":18,
}
var SESSION_DATA=map[string]interface{}{
"123":USERINFO_DATA,
}
// invalid composite literal type: interface{}
// 参考链接:https://www.itbaoku.cn/post/809967.html

func main(){
router := gin.Default()

// 模拟登录
router.GET("/login-mock",func(c *gin.Context){
// 设置/配置 cookie
c.SetCookie("userId","123",24 * 60 * 60 *1000,"/","localhost",false,true)
str := ""
username,err := c.GetQuery("username")
if !err{
// 模拟登录失败
str = "login failed"
} else {
// 模拟登录成功
userId,err := c.Cookie("userId")
if err != nil{
userId = "CookieNotSet"
}
fmt.Printf("userId is :%s\n",userId)
// 根据userId设置对应的用户信息
userInfo := SESSION_DATA[userId]
// 添加用户名
userInfo.(map[string]interface{})["username"] = username
str = "login ok"
}
c.JSON(http.StatusOK,gin.H{
"errno":0,
"message":str,
})
})
router.Run(":3000")
}

结果:

登录实现方案与实践_服务端_16

登录实现方案与实践_登录方案实践_17

参考链接:

https://www.jianshu.com/p/f7ea4e84e191

1.7 完善登录功能-对接数据库

这里就不采用前面6小节的代码了,重新写一下代码。

需求描述:

在首次登录,访问login接口的时候,会将用户的基本信息存储到session中,然后访问sayHello接口的时候,如果登录状态有效,那么返回数据就是Hello 用户名。

代码:(借鉴了见参考链接,做了部分修改)

type LoginForm struct {
Username string `json:"user" binding:"required,min=3,max=10"`
Password string `json:"password" binding:"required"`
}

var db = &LoginForm{Username: "keegan", Password: "123456"}

func getCurrentUser(c *gin.Context) (userInfo LoginForm) {
session := sessions.Default(c)
userInfo = session.Get("currentUser").(LoginForm)
return
}

func setCurrentUser(c *gin.Context, userInfo LoginForm) {
session := sessions.Default(c)
session.Set("currentUser", userInfo)
session.Save()
}

func setupRouter(r *gin.Engine) {
r.POST("/login", func(c *gin.Context) {
var loginForm LoginForm
if c.ShouldBind(&loginForm) != nil {
c.String(http.StatusOK, "参数错误")
return
}
if loginForm.Username == db.Username && loginForm.Password == db.Password {
setCurrentUser(c, *db)
c.String(http.StatusOK, "登录成功")
} else {
c.String(http.StatusOK, "登录失败")
}
})

r.GET("/sayHello",middleware.Cookie(),func(c *gin.Context) {
userInfo := getCurrentUser(c)
c.String(http.StatusOK, "Hello "+userInfo.Username)
})
}

func main() {
// 使用gob注册结构体,序列化对象保存到本地
gob.Register(LoginForm{})
r := gin.Default()
store := cookie.NewStore([]byte("snaosnca"))
r.Use(sessions.Sessions("SESSIONID", store))
setupRouter(r)
// Listen and Server in 0.0.0.0:8080
r.Run(":8080")
}

截图:

登录实现方案与实践_用户信息_18

登录实现方案与实践_用户名_19

其他参考链接:

​go mongodb增删改查​

go session 获取当前用户

1.8 完善登录功能-重写登录中间件

代码:

func Cookie() gin.HandlerFunc {
return func(c *gin.Context) {
session := sessions.Default(c)

if session.Get("currentUser") == nil {
c.Abort()
c.String(http.StatusOK, "未登录无权限")
} else {
c.Next()
}
}
}

目录结构:

登录实现方案与实践_登录方案实践_20

登录实现方案与实践_用户名_21

其他:

​序列化和反序列化​

序列化是将内存中的数据变成可传输的对象+可永久存储的对象的过程,

反序列化是将对象重新读到内存中。

…没有实践,就没有发言权…