Gin 应用多实例部署session问题、session参数与刷新

一、Gin Session 存储的实现方案

  • cookie:基于cookie的实现,不安全,一般不会使用。
  • gorm:基于 GORM 的实现
  • memcached:基于 Memcached 的实现
  • memstore:基于内存的实现,一般单实例部署用的比较多,或者本地测试。
  • mongo:基于 MongoDB 的实现
  • postgres:基于 PostgreSQL 的实现
  • redis:基于 Redis 的实现,多实例部署,应该无脑选 redis 实现。
  • tester:用于测试的实现

其实Gin 中的session 是通过github.com/gorilla/sessions实现的,只不过做了二次封装。

二、memstore:基于内存的实现

2.1 基本使用

memstoregithub.com/gin-contrib/sessions库提供的一个基于内存的Session存储后端。它将Session数据存储在应用程序的内存中,适用于小型应用或用于开发和测试目的。以下是一个使用memstore的简单示例:

  1. 安装gin-contrib/sessions库:

    go get -u github.com/gin-contrib/sessions
    
  2. 使用memstore存储Session:

    package main
    
    import (
    	"github.com/gin-contrib/sessions"
    	"github.com/gin-contrib/sessions/memstore"
    	"github.com/gin-gonic/gin"
    )
    
    func main() {
    	// 初始化Gin引擎
    	router := gin.Default()
        // 用内存存储 session
    	// 这是基于内存的实现,第一个参数是 authentication key,最好是 32 或者64 位
    	//第二个参数是 encryption key
    	store := memstore.NewStore([]byte("moyn8y9abnd7q4zkq2m73yw8tu9j5ixm"),[]byte("o6idlo2cb9f9pb6h46fimllw481ldebi"))
    	router.Use(sessions.Sessions("mysession", store))
    
    	// 路由示例
    	router.GET("/set", func(c *gin.Context) {
    		// 设置Session
    		session := sessions.Default(c)
    		session.Set("key", "value")
    		session.Save()
    
    		c.JSON(200, gin.H{"message": "Session set"})
    	})
    
    	router.GET("/get", func(c *gin.Context) {
    		// 获取Session
    		session := sessions.Default(c)
    		value := session.Get("key")
    
    		c.JSON(200, gin.H{"key": value})
    	})
    
    	// 启动服务
    	router.Run(":8080")
    }
    
    

    在上述示例中,我们使用了memstore.NewStore创建一个基于内存的Session存储后端。记得在实际应用中,根据实际需求选择适当的Session存储后端,例如,在生产环境中可能更常见的是使用像Redis这样的持久化存储。

    请注意,memstore是基于内存的,如果应用程序重新启动,所有存储在内存中的Session数据将被清除。因此,它最适用于短期的Session需求,而不适用于长期的数据存储。在实际生产环境中,需要根据应用程序的需求选择合适的Session存储后端。

2.2 关键参数

我们通过NewStore入口进入,可以看到,官方要求传入鉴权和加密的Key对于Key的长度越长越复杂越安全。

在正常情况下,要传入两个关键参数。第一个参数是 认证密钥(authentication key),最好是 32 或者 64 位。第二个参数是 加密密钥(encryption key)。

// 用内存存储 session
// 这是基于内存的实现,第一个参数是 authentication key,最好是 32 或者64 位
//第二个参数是 encryption key
store := memstore.NewStore([]byte("moyn8y9abnd7q4zkq2m73yw8tu9j5ixm"),[]byte("o6idlo2cb9f9pb6h46fimllw481ldebi"))
// cookie 的名字叫做sessions
ser.Use(sessions.Sessions("sessions", store))// 登录校验
ser.Use(middleware.NewLoginMiddlewareBuilder().Build())

三、使用redis:多实例部署

3.1 使用redis优势

在分布式环境下(包括单例应用多实例部署),都需要确保 Session 在每一个实例上都可以访问到,而单节点只能访问当前环境的Session。

3.2 基本使用

在使用Redis作为Session存储时,你需要使用Gin框架的github.com/gin-contrib/sessions中间件,并选择一个支持Redis的Session存储后端,比如github.com/gin-contrib/sessions/redis

以下是一个基本的使用示例:

  1. 安装依赖:

    go get -u github.com/gin-contrib/sessions
    go get -u github.com/gin-contrib/sessions/redis
    
  2. 使用Redis存储Session:

    package main
    
    import (
    	"github.com/gin-contrib/sessions"
    	"github.com/gin-contrib/sessions/redis"
    	"github.com/gin-gonic/gin"
    )
    
    func main() {
    	// 初始化Gin引擎
    	router := gin.Default()
    
    	// 使用Redis存储Session
    	store, err := redis.NewStore(
    		16,               // 最大空闲链接数量,过大会浪费,过小将来会触发性能瓶颈
    		"tcp",            // 指定与Redis服务器通信的网络类型,通常为"tcp"
    		"localhost:6379", // Redis服务器的地址,格式为"host:port"
    		"",               // Redis服务器的密码,如果没有密码可以为空字符串
    		[]byte("95osj3fUD7fo0mlYdDbncXz4VD2igvf0"), // authentication key
    		[]byte("0Pf2r0wZBpXVXlQNdpwCXN4ncnlnZSc3"), // encryption key
    	)
    
    	if err != nil {
    		panic(err)
    	}
    
    	// 设置Session中间件
    	router.Use(sessions.Sessions("mysession", store))
    
    	// 路由示例
    	router.GET("/set", func(c *gin.Context) {
    		// 设置Session
    		session := sessions.Default(c)
    		session.Set("key", "value")
    		session.Save()
    
    		c.JSON(200, gin.H{"message": "Session set"})
    	})
    
    	router.GET("/get", func(c *gin.Context) {
    		// 获取Session
    		session := sessions.Default(c)
    		value := session.Get("key")
    
    		c.JSON(200, gin.H{"key": value})
    	})
    
    	// 启动服务
    	router.Run(":8080")
    }
    
    

    在上述示例中,我们使用了redis.NewStore创建一个基于Redis的Session存储后端。你需要提供Redis服务器的地址、密码和密钥等信息。

四、信息安全的三个核心概念

  1. 身份认证(Authentication): 身份认证是确认用户或系统的身份是否合法的过程。在身份认证中,用户提供的身份信息(例如用户名和密码、生物特征等)被验证以确定其是否有权访问系统或资源。常见的身份认证方式包括用户名密码认证、多因素认证(例如使用手机验证码或硬件令牌)、生物特征认证等。
  2. 数据加密(Encryption): 数据加密是通过使用算法将信息转化为密文,以确保只有具备正确密钥的人或系统能够解密和访问原始信息。数据加密对于保护敏感信息、防止数据泄露和维护隐私非常重要。常见的加密算法包括对称加密(同一个密钥用于加密和解密)和非对称加密(使用一对密钥,一个用于加密,另一个用于解密)。
  3. 权限控制(Authorization): 权限控制是确保用户或系统在身份认证成功后只能访问其被授权的资源和执行其被授权的操作的过程。这包括定义用户或系统的角色、分配权限、限制访问范围等。权限控制有助于防止未经授权的访问和确保系统的安全性。

五、Gin Session 参数

5.1 参数介绍

在Gin框架中,Session的参数可以通过Options方法来传入OptionOptions方法用于配置Session的一些参数,以满足应用程序的需求。

参数详细解释:

字段 含义 示例值
Path Cookie的路径 "/"
Domain Cookie的域 "your-domain.com"
MaxAge 最大生存时间(秒) 3600
Secure 是否仅通过HTTPS传输 true
HttpOnly 是否禁止通过JavaScript访问Cookie true
SameSite SameSite属性 http.SameSiteLaxMode

举个例子:

package main

import (
	"github.com/gin-contrib/sessions"
	"github.com/gin-gonic/gin"
	"net/http"
)

func main() {
	// 初始化Gin引擎
	router := gin.Default()

	// 配置Session
	store := sessions.NewCookieStore([]byte("your-secret-key"))
	store.Options(sessions.Options{
		Path:     "/",
		Domain:   "your-domain.com",
		MaxAge:   3600, // 设置为3600秒,即1小时
		Secure:   true, // 仅通过HTTPS传输Cookie
		HttpOnly: true, // 禁止通过JavaScript访问Cookie
		SameSite: http.SameSiteLaxMode, // SameSite属性,限制在顶级导航时发送
	})
	router.Use(sessions.Sessions("mysession", store))

	// 路由示例
	router.GET("/set", func(c *gin.Context) {
		// 设置Session
		session := sessions.Default(c)
		session.Set("key", "value")
		session.Save()

		c.JSON(200, gin.H{"message": "Session set"})
	})

	router.GET("/get", func(c *gin.Context) {
		// 获取Session
		session := sessions.Default(c)
		value := session.Get("key")

		c.JSON(200, gin.H{"key": value})
	})

	// 启动服务
	router.Run(":8080")
}

六、Session 自动刷新

实现方式,在中间件中设置一个更新时间:

// LoginMiddlewareBuilder 结构体的 Build 方法,用于构建 Gin 中间件
func (l *LoginMiddlewareBuilder) Build() gin.HandlerFunc {
	// 使用 gob 注册 time.Now(),以便在 Session 中存储 time.Time 类型
	gob.Register(time.Now())

	// 返回一个 Gin 中间件函数
	return func(ctx *gin.Context) {
		// 检查当前请求路径是否为不需要登录校验的路径
		if ctx.Request.URL.Path == "/users/login" ||
			ctx.Request.URL.Path == "/users/signup" {
			// 如果是不需要登录校验的路径,直接返回,不进行后续的登录检查
			return
		}

		// 获取默认的 Session
		sess := sessions.Default(ctx)

		// 获取 Session 中存储的 userId
		id := sess.Get("userId")

		// 如果 userId 不存在,说明用户未登录,返回未授权状态码
		if id == nil {
			ctx.AbortWithStatus(http.StatusUnauthorized)
			return
		}

		// 获取 Session 中的 updateTime
		updateTime := sess.Get("update_time")

		// 设置 Session 的 userId,并配置 Session 的过期时间为 60 秒
		sess.Set("userId", id)
		sess.Options(sessions.Options{
			MaxAge: 60,
		})

		now := time.Now()

		// 如果 updateTime 为空,说明是第一次登录,设置 update_time 并保存 Session
		if updateTime == nil {
			sess.Set("update_time", now)
			if err := sess.Save(); err != nil {
				panic(err)
			}
		}

		// 如果 updateTime 不为空,说明已经登录过,检查是否超过 10 秒,超过则刷新 update_time 并保存 Session
		updateTimeVal, _ := updateTime.(time.Time)
		if now.Sub(updateTimeVal) > time.Second*10 {
			sess.Set("update_time", now)
			if err := sess.Save(); err != nil {
				panic(err)
			}
		}
	}
}

缺点:由于这种方式每次都要从Redis中读写数据,在高并发中并不适合。

本文作者:贾维斯Echo

本文链接:https://www.cnblogs.com/taoxiaoxin/p/17991891

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   贾维斯Echo  阅读(589)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 404 not found REOL
  2. 2 稻香 稻香 (2015中国好声音第四季现场) - 周杰伦;徐林;Will Jay
稻香 - 稻香 (2015中国好声音第四季现场) - 周杰伦;徐林;Will Jay
00:00 / 00:00
An audio error has occurred, player will skip forward in 2 seconds.

稻香 (2015中国好声音第四季现场) - 周杰伦/徐林/Will Jay

词:周杰伦

曲:周杰伦

对这个世界如果你有太多的抱怨

对这个世界如果你有太多的抱怨

跌倒了就不敢继续往前走

为什么人要这么的脆弱堕落

请你打开电视看看

多少人为生命在努力勇敢的走下去

我们是不是该知足

珍惜一切就算没有拥有

还记得你说家是唯一的城堡

还记得你说家是唯一的城堡

随着稻香河流继续奔跑

微微笑小时候的梦我知道

不要哭让萤火虫带着你逃跑

不要哭让萤火虫带着你逃跑

乡间的歌谣永远的依靠

回家吧回到最初的美好

不要这么容易就想放弃

不要这么容易就想放弃

就像我说的

追不到的梦想换个梦不就得了

为自己的人生鲜艳上色

先把爱涂上喜欢的颜色

笑一个吧功成名就不是目的

笑一个吧功成名就不是目的

让自己快乐快乐这才叫做意义

童年的纸飞机

现在终于飞回我手里

所谓的那快乐

所谓的那快乐

赤脚在田里追蜻蜓追到累了

偷摘水果被蜜蜂给叮到怕了

谁在偷笑呢

我靠着稻草人吹着风唱着歌睡着了

哦哦哦哦

珍惜一切就算没有拥有

珍惜一切就算没有拥有

还记得你说家是唯一的城堡

还记得你说家是唯一的城堡

随着稻香河流继续奔跑

微微笑小时候的梦我知道

不要哭让萤火虫带着你逃跑

不要哭让萤火虫带着你逃跑

乡间的歌谣永远的依靠

回家吧回到最初的美好

还记得你说家是唯一的城堡

还记得你说家是唯一的城堡

随着稻香河流继续奔跑

微微笑小时候的梦我知道

不要哭让萤火虫带着你逃跑

不要哭让萤火虫带着你逃跑

乡间的歌谣永远的依靠

回家吧回到最初的美好