GO WEB 学习笔记(四)Session和数据存储

本文详细介绍了HTTP中的Session和Cookie的工作原理,以及在Go语言中如何操作Cookie和使用Session。通过创建全局唯一标识符、开辟数据存储空间并将SessionID通过Cookie传递给客户端来实现Session管理。此外,还讨论了Session过期处理和存储策略。针对Session劫持的安全问题,提出了防止Cookie被XSS读取和定期更换SessionID的解决方案。
摘要由CSDN通过智能技术生成

Session和Cookie

  • Session和Cookie是如何来的?
  • 首先我们想想这样一个场景,你登陆微博个人主页的时候,是不是需要登录,你登陆之后,是不是就可以跳转来到你的个人主页,这个时候还没有问题产生,因为你登陆输入了用户和密码,自然而然就可以跳转到你对应的个人页面,但是问题就来了,你要去其他页面并且保存你登陆的状态怎么验证,因为http请求是没有状态的,他不知道有没有登录,这个时候怎么办呢?
  • 所以就需要cookie和session 来保存我们登陆的状态
  • Cookie:本地计算机保存一些用户操作的历史信息,并在用户再次访问该站点时浏览器通过HTTP协议将本地Cookie内容发送给服务器,从而完成验证,或继续上一步操作。
    • Cookie是有时间限制的,根据生命周期不同分为会话Cookie和持久Cookie
      • 如果不设置过期时间,则表示这个Cookie生命周期从创建到浏览器关闭为止,只要关闭浏览器窗口,Cookie就消失。
      • 如果设置了过期时间,浏览器就会把Cookie保存到硬盘上,关闭后再次打开浏览器,这些Cookie依然有效直到超过设定的过期时间。
        在这里插入图片描述
  • Session:在服务器上保存用户操作的历史信息。服务器使用Session ID来标识Session,Session ID由服务器负责产生,保证随机性与唯一性,相当于一个随机密钥,避免在握手或传输中暴露用户真实密码。但该方式下,仍然需要将发送请求的客户端与Session进行对应,所以可以借助Cookie机制来获取客户端的标识(即Session ID),也可以通过GET方式将ID提交给服务器。
    在这里插入图片描述

Go 语言对Cookie的操作

  1. 设置Cookie:
http.setCookie(w ResponseWriter, cookie *Cookie)
cookie := http.Cookie()
type Cookie struct {
	Name  string
	Value string

	Path       string    // optional
	Domain     string    // optional
	Expires    time.Time // optional
	RawExpires string    // for reading cookies only

	// MaxAge=0 means no 'Max-Age' attribute specified.
	// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
	// MaxAge>0 means Max-Age attribute present and given in seconds
	MaxAge   int
	Secure   bool
	HttpOnly bool
	SameSite SameSite
	Raw      string
	Unparsed []string // Raw text of unparsed attribute-value pairs
}

  1. 读取Cookie:请求.Cookie()

Go 语言如何使用Session

  1. Session创建过程:
    • 生成全局唯一标识符
    • 开辟数据存储空间
    • 将Session的全局唯一标识符发送给客户端
      • Cookie服务端通过设置Set-cookie头就可以将Session的标识符传送到客户端,客户端此后的每一次请求都会带上这个标识符,另外一般包含Session信息的Cookie会将失效时间设置为0,即浏览器进程有效时间。
      • URL重写,所谓URL重写,就是返回给用户的页面里的所有URL后面追加Session标识符,这样用户在收到响应之后,无论点击响应页面里的哪个链接或提交表单,都会自动带上Session标识符。
  2. 实现Session管理:
    • 全局Session管理器
      • Manager:管理器
      • Provider:存储方式
        • SessionInit函数实现Session的初始化,操作成功则返回此新的Session变量。
        • SessionRead函数返回sid所代表的Session变量,如果不存在,那么将以sid为参数调用SessionInit函数创建并返回一个新的Session变量。
        • SessionDestroy函数用来销毁sid对应的Session变量。
        • SessionGC根据maxLifeTime来删除过期的数据。
    • 保证SessionID的全局唯一性
    • 为每个客户关联一个Session
    • Session的存储
    • Session过期处理
// 第一步定一个全局的管理器
type Manager struct {
	cookieName  string
	lock        sync.Mutex //互斥锁
	provider    Provider   //存储session方式
	maxLifeTime int64      //有效期
}
//创建对应的Session管理器
func NewManger(provideName, cookieName string, maxlifeTime int64)(*Manager,error){
	provider, ok = provides(provideName)
	if !ok {
		return nil,fmt.Errorf("session: 找不到对应存储方法!")
	}
	return &Manager{
		provider: provider,
		cookieName: cookieName,
		maxLifeTime: maxlifeTime,
	}
}

// 第二步 session存储方式接口
type Provider interface {
	//初始化一个session,sid根据需要生成后传入
	SessionInit(sid string) (Session, error)
	//根据sid,获取session
	SessionRead(sid string) (Session, error)
	//销毁session
	SessionDestroy(sid string) error
	//回收
	SessionGC(maxLifeTime int64)
}
//Session操作接口
type Session interface {
	Set(key, value interface{}) error
	Get(key interface{}) interface{}
	Delete(ket interface{}) error
	SessionID() string
}

//注册 由实现Provider接口的结构体调用
func Register(name string, provide Provider) {
	if provide == nil {
		panic("session: Register provide is nil")
	}
	if _, ok := provides[name]; ok {
		panic("session: Register called twice for provide " + name)
	}
	provides[name] = provide
}

var provides = make(map[string]Provider)

//3 全局唯一的SessionID
func (manager *Manager) sessionId() string {
	b := make([]byte, 32)
	if _, err := io.ReadFull(rand.Reader, b); err != nil {
		return ""
	}
	//加密
	return base64.URLEncoding.EncodeToString(b)
}

//4 为每个客户关联一个Session (Session创建)
 //判断当前请求的cookie中是否存在有效的session,存在返回,否则创建
func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session Session) {
	manager.lock.Lock() //加锁
	defer manager.lock.Unlock()
	cookie, err := r.Cookie(manager.cookieName)
	if err != nil || cookie.Value == "" {
		//创建一个
		sid := manager.sessionId()
		session, _ = manager.provider.SessionInit(sid)
		cookie := http.Cookie{
			Name:     manager.cookieName,
			Value:    url.QueryEscape(sid), //转义特殊符号@#¥%+*-等
			Path:     "/",
			HttpOnly: true,
			MaxAge:   int(manager.maxLifeTime),
			Expires:  time.Now().Add(time.Duration(manager.maxLifeTime)),
			//MaxAge和Expires都可以设置cookie持久化时的过期时长,Expires是老式的过期方法,
			// 如果可以,应该使用MaxAge设置过期时间,但有些老版本的浏览器不支持MaxAge。
			// 如果要支持所有浏览器,要么使用Expires,要么同时使用MaxAge和Expires。


		}
		http.SetCookie(w, &cookie)
	} else {
		sid, _ := url.QueryUnescape(cookie.Value) //反转义特殊符号
		session, _ = manager.provider.SessionRead(sid)
	}
	return session
}

//Session过期处理
//销毁session 同时删除cookie
func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request) {
	cookie, err := r.Cookie(manager.cookieName)
	if err != nil || cookie.Value == "" {
		return
	} else {
		manager.lock.Lock()
		defer manager.lock.Unlock()
		sid, _ := url.QueryUnescape(cookie.Value)
		manager.provider.SessionDestroy(sid)
		expiration := time.Now()
		cookie := http.Cookie{
			Name:     manager.cookieName,
			Path:     "/",
			HttpOnly: true,
			Expires:  expiration,
			MaxAge:   -1}
		http.SetCookie(w, &cookie)
	}
}
//Session销毁
func (manager *Manager) GC() {
	manager.lock.Lock()
	defer manager.lock.Unlock()
	manager.provider.SessionGC(manager.maxLifeTime)
	time.AfterFunc(time.Duration(manager.maxLifeTime), func() { manager.GC() })
}
  1. Session 存储
var pder = &FromMemory{list: list.New()}

func init() {
	pder.sessions = make(map[string]*list.Element, 0)
	//注册  memory 调用的时候一定有一致
	session.Register("memory", pder)
}

//session实现
type SessionStore struct {
	sid              string                      //session id 唯一标示
	LastAccessedTime time.Time                   //最后访问时间
	value            map[interface{}]interface{} //session 里面存储的值
}

//设置
func (st *SessionStore) Set(key, value interface{}) error {
	st.value[key] = value
	pder.SessionUpdate(st.sid)
	return nil
}

//获取session
func (st *SessionStore) Get(key interface{}) interface{} {
	pder.SessionUpdate(st.sid)
	if v, ok := st.value[key]; ok {
		return v
	} else {
		return nil
	}
	return nil
}

//删除
func (st *SessionStore) Delete(key interface{}) error {
	delete(st.value, key)
	pder.SessionUpdate(st.sid)
	return nil
}
func (st *SessionStore) SessionID() string {
	return st.sid
}

//session来自内存 实现
type FromMemory struct {
	lock     sync.Mutex               //用来锁
	sessions map[string]*list.Element //用来存储在内存
	list     *list.List               //用来做 gc
}

func (frommemory *FromMemory) SessionInit(sid string) (session.Session, error) {
	frommemory.lock.Lock()
	defer frommemory.lock.Unlock()
	v := make(map[interface{}]interface{}, 0)
	newsess := &SessionStore{sid: sid, LastAccessedTime: time.Now(), value: v}
	element := frommemory.list.PushBack(newsess)
	frommemory.sessions[sid] = element
	return newsess, nil
}

func (frommemory *FromMemory) SessionRead(sid string) (session.Session, error) {
	if element, ok := frommemory.sessions[sid]; ok {
		return element.Value.(*SessionStore), nil
	} else {
		sess, err := frommemory.SessionInit(sid)
		return sess, err
	}
	return nil, nil
}

func (frommemory *FromMemory) SessionDestroy(sid string) error {
	if element, ok := frommemory.sessions[sid]; ok {
		delete(frommemory.sessions, sid)
		frommemory.list.Remove(element)
		return nil
	}
	return nil
}

func (frommemory *FromMemory) SessionGC(maxLifeTime int64) {
	frommemory.lock.Lock()
	defer frommemory.lock.Unlock()
	for {
		element := frommemory.list.Back()
		if element == nil {
			break
		}
		if (element.Value.(*SessionStore).LastAccessedTime.Unix() + maxLifeTime) <
			time.Now().Unix() {
			frommemory.list.Remove(element)
			delete(frommemory.sessions, element.Value.(*SessionStore).sid)
		} else {
			break
		}
	}
}
func (frommemory *FromMemory) SessionUpdate(sid string) error {
	frommemory.lock.Lock()
	defer frommemory.lock.Unlock()
	if element, ok := frommemory.sessions[sid]; ok {
		element.Value.(*SessionStore).LastAccessedTime = time.Now()
		frommemory.list.MoveToFront(element)
		return nil
	}
	return nil
}
  1. 预防Session劫持
    • 首先先了解什么是Session劫持,Session劫持是一种广泛存在的比较严重的安全威胁,在Session技术中,客户端和服务端通过Session得标识符来维护会话,但这个标识符 很容易就能被嗅探到,从而被其他人利用,下面是例子:
      在这里插入图片描述
    • 虽然换了浏览器,但是我们却获得了SessionID,然后模拟了Cookie存储的过程。这个例子是在同一台计算机上做的,不过即使换用两台来做,其结果仍然一样。此时如果交替点击两个浏览器里的链接你会发现它们其实操纵的是同一个计数器。处firefox盗用了chrome和goserver之间的维持会话的钥匙,即goSessionid,这是一种类型的“会话劫持”。在goserver看来,它从http请求中得到了一个gosessionid,由于HTTP协议的无状态性,它无法得知这个gosessionid是从chrome那里“劫持”来的,它依然会去查找对应的Session,并执行相关计算。与此同时chrome也无法得知自己保持的会话已经被“劫持”。
    • 所以我们应该如何预防呢?
    • 第一个解决方案是SessionID的值只允许Cookie设置,而不是通过URL重置方式设置,同时设置Cookie的httponly为true,然后在每个请求加上token,防止form重复提交的功能,从而保证用户的请求唯一性
      • 作用一:防止Cookie被XSS读取从而引起Session劫持
      • 作用二:Cookie设置不会像URL重置方式那么容易获取SessionID
    • 第二个解决方案是间隔生成新的SID,给Session额外设置一个创建时间的值,一旦过了一定的时间,销毁这个SessionID,重新生成新的Session,这样可以一定程度上防止Session劫持的问题
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值