• Gin框架之Session的使用(详解)


    前言

    本文主要介绍 go开源框架gin 是如何使用session的。以及前端 vue3 + axios@0.27 是如何配合session一起使用的

    主要介绍内容包括这些:

    1. session的基本原理
    2. gin配置session
    3. axios 如何设置携带cookie (chrome版本 < 80)
    4. axios 如何在跨域请求时携带cookie(chrome版本 < 80)
    5. chrome浏览器版本 >= 80 时,axios 不携带cookie的解决方案
    6. session的一些基本配置操作
    7. session的Flashes讲解

    正文

    session的基本原理

    http协议是无状态的,就是说你在请求服务器同时,张三也在请求服务器,这时候服务器是不知道哪个请求是你,哪个请求张三(除非你们携带了一些识别字段,不过这属于业务层内容,不在讨论范围,我们单说http协议)。

    为了让服务器知道哪个请求时你的,哪个是张三的。这时候就有了session。

    session和cookie是不分家的。我们每次说到session,其实默认就是要使用cookie了。

    我们举例说明一下什么是session:

    你要去逛澡堂,刚进门来到服务台时你是没有开储物柜的钥匙的。当你交钱了,服务台就给你一把储物柜的钥匙,这时储物柜就和你的钥匙绑定了。你去游泳、搓澡,你一直会把钥匙带在身上。这时候你不管什么时候去服务台,只要你拿着钥匙,就会认识你,你拿着钥匙可以随便的到储物柜中拿取自己的东西。你要离开时,把钥匙柜服务台就可以了。

    在程序里是怎么实现上面的过程的呢?
    服务器就是服务台,储物柜就是session仓库,钥匙就是我们的凭证。携带钥匙其实就是cookie传输。

    第一次登录,服务器给客户端颁发一个唯一的sessionId, 并通过http的响应头返回。客户端(浏览器)发现返回的数据中有cookie数据就把这个cookie数据存放到内存。下次再发送http请求时,把内存中的cookie数据再塞到http请求头中,一并发给服务器,服务器在解析请求时,发现请求头中有cookie,就开始识别cookie中的sessionId,拿到sessionId,我们就知道这个请求时由哪个客户端发送来的了。

    gin配置session

    gin框架在处理session时有专门的中间件,我们可以直接使用。

    中间件: github.com/gin-contrib/sessions , 我们直接安装依赖: go get github.com/gin-contrib/sessions 即可引入使用

    sessions使用教程

    我们参考官网案例:

    package main
    
    import (
            // 导入session包
    	"github.com/gin-contrib/sessions"
           // 导入session存储引擎
    	"github.com/gin-contrib/sessions/cookie"
            // 导入gin框架包
    	"github.com/gin-gonic/gin"
    )
    
    func main() {
    	r := gin.Default()
    
            // 创建基于cookie的存储引擎,shuiche 参数是用于加密的密钥,可以随便填写
    	store := cookie.NewStore([]byte("shuiche"))
    
            // 设置session中间件,参数mysession,指的是session的名字,也是cookie的名字
           // store是前面创建的存储引擎
    	r.Use(sessions.Sessions("mysession", store))
    
    	r.GET("/test", func(c *gin.Context) {
                    // 初始化session对象
    		session := sessions.Default(c)
                    
                    // 通过session.Get读取session值
                    // session是键值对格式数据,因此需要通过key查询数据
    
    		if session.Get("hello") != "world" {
                            // 设置session数据,()
    			session.Set("hello", "world")
                            // 删除session数据
                            session.Delete("tizi365")
                            // 保存session数据
    			session.Save()
                            // 删除整个session
                            // session.Clear()
    		}
                    
    		c.JSON(200, gin.H{"hello": session.Get("hello")})
    	})
    	r.Run(":8000")
    }
    
    

    使用sessions 中间件注意要点:

    1. session 仓库其实就是一个 map[interface]interface 对象,所有 session可以存储任意数据
    2. session 使用的编解码器是自带的gob,所以存储类似: struct、map 这些对象时需要先注册对象,不然会报错 gob: type not registered for...
    3. session 存储引擎支持: cookie、内存、mongodb、redis、postgres、memstore、memcached 以及 gorm 支持的各类数据库(mysql、sqlite)
    4. session 在创建时有一个配置项,可以配置session过期时间、cookie、domain、secure、path等参数
    5. 调用 session 方法: Set()、 Delete()、 Clear()、方法后,必须调用一次 Save() 方法。否则session数据不会更新

    gob注册案例

    type User struct{
      Name string
    }
    
    gob.Register(User{})
    

    session配置项案例

    // store 就是前面创建的存储引擎
    store.Options(sessions.Options{
    		Secure:   true,
    		SameSite: 4,
    		Path:     "/",
    		MaxAge:   m.MaxAge,
    	})
    

    axios 如何设置携带cookie (chrome版本 < 80)

    axios请求默认是不携带 Cookie 的,如果需要携带 Cookie 前端需要配置

    // 全局配置
    axios.defaults.withCredentials = true
    
    // 实例配置
    const service = axios.create({
    	baseURL: process.env.BASE_API,
    	timeout: 5000,
    	withCredentials: true  //  配置项
    })
    
    // 单个路由配置
    axios.get('/test', {
      withCredentials: true
    })
    

    axios 如何在跨域请求时携带cookie(chrome版本 < 80)

    涉及到跨域,我们就要想到要在服务器端做处理
    我们给服务器开启 CORS 跨域,我们给gin添加 CORS 中间件

    跨域配置中间件

    func(c *gin.Context) {
    		method := c.Request.Method
    		origin := c.GetHeader("Origin")
    		c.Header("Access-Control-Allow-Origin", origin) // 注意这一行,不能配置为通配符“*”号
    		c.Header("Access-Control-Allow-Credentials", "true") // 注意这一行,必须设定为 true
    		c.Header("Access-Control-Allow-Headers", "Access-Control-Allow-Headers,Cookie, Origin, X-Requested-With, Content-Type, Accept, Authorization, Token, Timestamp, UserId") // 我们自定义的header字段都需要在这里声明
    		c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS,DELETE,PUT")
    		c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type,cache-control")
    
    		// 放行所有OPTIONS方法
    		if method == "OPTIONS" {
    			//c.AbortWithStatus(http.StatusNoContent)
    			c.AbortWithStatus(http.StatusOK)
    		}
    		// 处理请求
    		c.Next()
    	}
    
    

    chrome浏览器版本 >= 80 时,axios 不携带cookie的解决方案

    如果我们的chrome浏览器版本高于80,我们会发现按照上面两步配置的cookie还是无法生效,这是因为chrome为了增加安全性修改了携带cookie的方式。

    这里分两种情况讲解:

    线上情况

    1. 设置: samesite: none,也就是session的store在设定options时设定的字段配置为:SameSite: 4,(这里4 就代表none)。
    2. secure 配置为 true
    3. 服务端开启https (必须)

    1、2 配置案例查看 上面的 session配置项案例

    开发环境

    用127.0.0.1地址或者localhost地址测试

    优化方案

    我们可以利用cookie机制发送sessionId。目前我正在考虑使用自定义header字段携带sessionId,基本逻辑:
    前端:请求返回后,判断是否有我们命名的session 名称的cookie,(就是创建session时的自定义名称:mysession),如果有将值存储到内存。 发送请求时,将存储的session值添加到http请求的header中。

    服务端:将校验session的中间件中的获取session 修改为从header中获取。

    因为chrome>80的浏览器是发不出去cookie,不是收不到cookie。我们就不用浏览器的字段携带cookie机制,搞成手动携带cookie

    session的一些基本配置操作

    设置session过期时间

    设置:store.Options(sessions.Options{MaxAge: 86400*30})

    session 刷新超时时间

    基本思路:

    每次获取到请求后, 我们将该用户的session重新赋值,并返回给客户端,客户端下次请求时携带新的session请求。

    当前 session 用户数量

    有时候我们系统需要统计当前在线用户数量,这是我们就不能用cookie或者内存存储session了,需要用数据库存储。

    我们把session存储到数据库后,通过查询当前没过期session数量就可以确定在线用户数

    注销session

    我们在退出登录时需要删掉session,只需要调用 sesssion的 Delete()方法即可, 记得调用 Save方法保存操作

    vue3 浏览器在关闭时发送退出登录请求

    在app.vue 文件中添加这样的代码, 其中:gapTime <= 12 中的 12 是经验值。vue文件使用 setup语法糖模式

    onMounted(() => {
        window.addEventListener('beforeunload', beforeunloadHandler)
        window.addEventListener('unload', unloadHandler)
    })
    
    function beforeunloadHandler (e) {
        beforeUnloadTime = new Date().getTime()
    }
    
    function unloadHandler (e) {
        gapTime = new Date().getTime() - beforeUnloadTime
        // 判断是窗口关闭还是刷新
        console.log('====gapTime=======', gapTime)
        if (gapTime <= 12) {
            // 如果是登录状态,关闭窗口前,移除用户
            navigator.sendBeacon(`/user/logout`, data) // 这里data是请求的参数。一个字符串,具体查看navigator.sendBeacon 使用方法
        }
        debugger
    }
    

    session的Flashes讲解

    我们在看 session库 源码时会发现还有两个函数:Flashes()AddFlash。简单说一下这两个函数作用

    Flash既闪存,不是我们浏览器里的那个flash插件,flash主要是存储一下临时数据。

    1. 我们调用 AddFlash时,会往flash里存储 一个键值对。
    2. 当我们调用 Flashs 读取了 闪存内容后,这个闪存数据就被删除了。(记得调用 Save()方法生效)

    后记

    本篇博文耗费了比较大的精力才研究透彻,如果对你有帮助,可以点个推荐,当然想请我喝杯可乐我也会很开心的哦

  • 相关阅读:
    ios手势复习值之换图片-转场动画(纯代码)
    ios地图小例子和手势的使用 供大家参考一下呦
    basicAnimation移动图形
    一个layer可以跟着画完的线移动ios程序 好玩啊。
    kvo深入浅出举例
    kvc简单实现
    block 浅析
    从相册中取图片
    绘图quartz之渐变
    绘图quartz之加水印
  • 原文地址:https://www.cnblogs.com/shuiche/p/16548964.html
Copyright © 2020-2023  润新知