资讯 小学 初中 高中 语言 会计职称 学历提升 法考 计算机考试 医护考试 建工考试 教育百科
栏目分类:
子分类:
返回
空麓网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
空麓网 > 计算机考试 > 软件开发 > 后端开发 > Go语言

Gin-session中Redis-based session的原理

Go语言 更新时间: 发布时间: 计算机考试归档 最新发布

Gin-session中Redis-based session的原理

Gin session库

本文讨论的Gin相关的session库为:https://github.com/gin-contrib/sessions

Redis-based session源码分析

gin-contrib/sessions这个库支持多种session的实现,例如:cookie-based session / Redis-based session / memcached Session。这里我们主要分析Redis-based session的实现代码。cookie-based session和Redis-based session的优缺点比较:

优点缺点
cookie-based简单,无需引入第三方存储,不占用服务器存储资源不安全,受浏览器限制,控制起来不灵活(例如从服务端设置“登出”用户)
Redis-based相对安全,控制更加灵活,适合存敏感信息占用服务器资源
session.Sessions中间件

在Gin中,通常以中间件的方式进行session管理。中间件的使用方式,我们应该都已经比较熟悉了。相应的HandlerFunc在gin-contrib/sessionssessions.go中:

func Sessions(name string, store Store) gin.HandlerFunc {
	return func(c *gin.Context) {
		s := &session{name, c.Request, store, nil, false, c.Writer}
		c.Set(DefaultKey, s)
		defer context.Clear(c.Request)
		c.Next()
	}
}

这里就干了一件事情:把request和store信息Set到Gin context中的Keys里,方便Handler中进行使用。这里的store为redis的接口。由此可以判断,业务Handler中肯定是从context里取出session,进行应用,我们后面来验证。

Handler中获取session

在Handler中,我们通过session := sessions.Default(c)来获取session,从而对session进行检查和处理。

sessions.go:

// shortcut to get session
func Default(c *gin.Context) Session {
	return c.MustGet(DefaultKey).(Session)
}
Handler中Set & Save session

Set()和Save()分别是设置和保存session信息的方法。gin-contrib/sessions的底层引用的是gorilla/sessions,Session struct的结构如下,其中的Values这个map用于存储session信息的键值对。

// Session stores the values and optional configuration for a session.
type Session struct {
	// The ID of the session, generated by stores. It should not be used for
	// user data.
	ID string
	// Values contains the user-data for the session.
	Values  map[interface{}]interface{}
	Options *Options
	IsNew   bool
	store   Store
	name    string
}

所以Set()方法更新的是上面的Values:

func (s *session) Set(key interface{}, val interface{}) {
	s.Session().Values[key] = val
	s.written = true
}

Save()方法是保存Values中的session信息。对于Redis-based session而言,是把session信息存入Redis。

func (s *session) Save() error {
	if s.Written() {
		e := s.Session().Save(s.request, s.writer)
		if e == nil {
			s.written = false
		}
		return e
	}
	return nil
}

再追一下s.Session().Save方法,它调用的是gorilla/sessions/sessions.go中的Save()方法:

// Save is a convenience method to save this session. It is the same as calling
// store.Save(request, response, session). You should call Save before writing to
// the response or returning from the handler.
func (s *Session) Save(r *http.Request, w http.ResponseWriter) error {
	return s.store.Save(r, w, s)
}

这里的注释写的也比较清楚,在Handler response返回之前需要调用这个Save()方法保存session信息。而且我们看到这里的Save已经是store的save了,对于Redis-based session,此时进行的是Redis的写操作。
gin-contrib/sessions的底层引用的Redis strore为boj/redisstore。我们再看看store中的Save()方法:

// Save adds a single session to the response.
func (s *RediStore) Save(r *http.Request, w http.ResponseWriter, session *sessions.Session) error {
	// Marked for deletion.
	if session.Options.MaxAge <= 0 {
		if err := s.delete(session); err != nil {
			return err
		}
		http.SetCookie(w, sessions.NewCookie(session.Name(), "", session.Options))
	} else {
		// Build an alphanumeric key for the redis store.
		if session.ID == "" {
			session.ID = strings.TrimRight(base32.StdEncoding.EncodeToString(securecookie.GenerateRandomKey(32)), "=")
		}
		if err := s.save(session); err != nil {
			return err
		}
		encoded, err := securecookie.EncodeMulti(session.Name(), session.ID, s.Codecs...)
		if err != nil {
			return err
		}
		http.SetCookie(w, sessions.NewCookie(session.Name(), encoded, session.Options))
	}
	return nil
}

Save()方法做了这样几件事:

  1. 生成了一个session id,这个session id一定对应了Redis中的key
  2. s.save方法调用了Redis的SETEX命令,所以这里是可以设置过期时间的,我们后面再说
  3. 基于session name和session id,进行了编码,并进行SetCookie操作。这里也比较直观了,session id存在了cookie当中。

cookie信息中传入了一个参数Options,这个Options来自Session struct用于存放一些配置项,例如MaxAge。MaxAge既对应了Redis中Key的过期时间(就是上面第一条SETEX对应的过期时间),也对应了cookie的过期时间。

Handler中Get session

在Handler中,实例化获取session的代码参看上文的sessions.Default(c)。

基于上面Set()方法和Save()方法的分析,不难猜出,Get()方法的功能就是基于cookie中的session id从Redis中获取session信息了。我们再看一下源码:
gin-contrib/sessions/sessions.go中的Get()方法是从Session的Values中获取信息:

func (s *session) Get(key interface{}) interface{} {
	return s.Session().Values[key]
}

Session()是找到对应的session对象。由于中间件中的session为nil,所以会调用store中的Get方法来获取具体的session信息:

func (s *session) Session() *sessions.Session {
	if s.session == nil {
		var err error
		s.session, err = s.store.Get(s.request, s.name)
		if err != nil {
			log.Printf(errorFormat, err)
		}
	}
	return s.session
}

我们再追一下 boj/redistore store中的Get方法,其中如果没有已存在的session name就会New一个新的。然后将session info放入s.sessions中:

// Get registers and returns a session for the given name and session store.
//
// It returns a new session if there are no sessions registered for the name.
func (s *Registry) Get(store Store, name string) (session *Session, err error) {
	if !isCookieNameValid(name) {
		return nil, fmt.Errorf("sessions: invalid character in cookie name: %s", name)
	}
	if info, ok := s.sessions[name]; ok {
		session, err = info.s, info.e
	} else {
		session, err = store.New(s.request, name)
		session.name = name
		s.sessions[name] = sessionInfo{s: session, e: err}
	}
	session.store = store
	return
}

boj/redistore store中的New()方法做了如下几件事:

  1. 从Cookie中获取session id并解码为Redis中存储的id
  2. 调用RedisStore的load方法(Redis的GET命令)来从Redis中获取到session信息
// New returns a session for the given name without adding it to the registry.
//
// See gorilla/sessions FilesystemStore.New().
func (s *RediStore) New(r *http.Request, name string) (*sessions.Session, error) {
	var (
		err error
		ok  bool
	)
	session := sessions.NewSession(s, name)
	// make a copy
	options := *s.Options
	session.Options = &options
	session.IsNew = true
	if c, errCookie := r.Cookie(name); errCookie == nil {
		err = securecookie.DecodeMulti(name, c.Value, &session.ID, s.Codecs...)
		if err == nil {
			ok, err = s.load(session)
			session.IsNew = !(err == nil && ok) // not new if no error and data available
		}
	}
	return session, err
}

以上就是对于session进行读/写/存的源码分析。至于用户鉴权(登录判断)需要业务代码来实现。

使用场景

Redis-based session的一个常用的场景是:

  1. 用户Login后将用户敏感信息存入Redis Session,并将session id返回,存入浏览器的cookie中
  2. 随后用户访问其他页面,服务端通过Auth中间件根据cookie中的session id从Redis中查询相应的用户信息。若用户信息Key-Value值存在,说明用户已登录,并且登录未超时,此时允许用户进行后续操作;否则Auth中间件的handler直接Abort并返回401,告知用户未登录(未经授权)。
参考资料
  1. https://github.com/gin-contrib/sessions
  2. https://github.com/gorilla/sessions
  3. https://github.com/boj/redistore
转载请注明:文章转载自 http://www.konglu.com/
本文地址:http://www.konglu.com/it/991532.html
免责声明:

我们致力于保护作者版权,注重分享,被刊用文章【Gin-session中Redis-based session的原理】因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理,本文部分文字与图片资源来自于网络,转载此文是出于传递更多信息之目的,若有来源标注错误或侵犯了您的合法权益,请立即通知我们,情况属实,我们会第一时间予以删除,并同时向您表示歉意,谢谢!

我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2023 成都空麓科技有限公司

ICP备案号:蜀ICP备2023000828号-2