Go gorilla securecookie库的安装使用详解

目录
  • 简介
  • 快速使用
  • 使用 JSON
  • 自定义编解码
  • Hash/Block 函数
  • 更换 Key
  • 总结

简介

cookie 是用于在 Web 客户端(一般是浏览器)和服务器之间传输少量数据的一种机制。由服务器生成,发送到客户端保存,客户端后续的每次请求都会将 cookie 带上。cookie 现在已经被多多少少地滥用了。很多公司使用 cookie 来收集用户信息、投放广告等。

cookie 有两大缺点:

  • 每次请求都需要传输,故不能用来存放大量数据;
  • 安全性较低,通过浏览器工具,很容易看到由网站服务器设置的 cookie。

gorilla/securecookie提供了一种安全的 cookie,通过在服务端给 cookie 加密,让其内容不可读,也不可伪造。当然,敏感信息还是强烈建议不要放在 cookie 中。

快速使用

本文代码使用 Go Modules。

创建目录并初始化:

$ mkdir gorilla/securecookie && cd gorilla/securecookie
$ go mod init github.com/darjun/go-daily-lib/gorilla/securecookie

安装gorilla/securecookie库:

$ go get github.com/gorilla/securecookie
package main
import (
  "fmt"
  "github.com/gorilla/mux"
  "github.com/gorilla/securecookie"
  "log"
  "net/http"
)
type User struct {
  Name string
  Age int
}
var (
  hashKey = securecookie.GenerateRandomKey(16)
  blockKey = securecookie.GenerateRandomKey(16)
  s = securecookie.New(hashKey, blockKey)
)
func SetCookieHandler(w http.ResponseWriter, r *http.Request) {
  u := &User {
    Name: "dj",
    Age: 18,
  }
  if encoded, err := s.Encode("user", u); err == nil {
    cookie := &http.Cookie{
      Name: "user",
      Value: encoded,
      Path: "/",
      Secure: true,
      HttpOnly: true,
    }
    http.SetCookie(w, cookie)
  }
  fmt.Fprintln(w, "Hello World")
}
func ReadCookieHandler(w http.ResponseWriter, r *http.Request) {
  if cookie, err := r.Cookie("user"); err == nil {
    u := &User{}
    if err = s.Decode("user", cookie.Value, u); err == nil {
      fmt.Fprintf(w, "name:%s age:%d", u.Name, u.Age)
    }
  }
}
func main() {
  r := mux.NewRouter()
  r.HandleFunc("/set_cookie", SetCookieHandler)
  r.HandleFunc("/read_cookie", ReadCookieHandler)
  http.Handle("/", r)
  log.Fatal(http.ListenAndServe(":8080", nil))
}

首先需要创建一个SecureCookie对象:

var s = securecookie.New(hashKey, blockKey)

其中hashKey是必填的,它用来验证 cookie 是否是伪造的,底层使用 HMAC(Hash-based message authentication code)算法。推荐hashKey使用 32/64 字节的 Key。

blockKey是可选的,它用来加密 cookie,如不需要加密,可以传nil。如果设置了,它的长度必须与对应的加密算法的块大小(block size)一致。例如对于 AES 系列算法,AES-128/AES-192/AES-256 对应的块大小分别为 16/24/32 字节。

为了方便也可以使用GenerateRandomKey()函数生成一个安全性足够强的随机 key。每次调用该函数都会返回不同的 key。上面代码就是通过这种方式创建 key 的。

调用s.Encode("user", u)将对象u编码成字符串,内部实际上使用了标准库encoding/gob。所以gob支持的类型都可以编码。

调用s.Decode("user", cookie.Value, u)将 cookie 值解码到对应的u对象中。

运行:

$ go run main.go

首先使用浏览器访问localhost:8080/set_cookie,这时可以在 Chrome 开发者工具的 Application 页签中看到 cookie 内容:

访问localhost:8080/read_cookie,页面显示name: dj age: 18

使用 JSON

securecookie默认使用encoding/gob编码 cookie 值,我们也可以改用encoding/jsonsecurecookie将编解码器封装成一个Serializer接口:

type Serializer interface {
  Serialize(src interface{}) ([]byte, error)
  Deserialize(src []byte, dst interface{}) error
}

securecookie提供了GobEncoderJSONEncoder的实现:

func (e GobEncoder) Serialize(src interface{}) ([]byte, error) {
  buf := new(bytes.Buffer)
  enc := gob.NewEncoder(buf)
  if err := enc.Encode(src); err != nil {
    return nil, cookieError{cause: err, typ: usageError}
  }
  return buf.Bytes(), nil
}
func (e GobEncoder) Deserialize(src []byte, dst interface{}) error {
  dec := gob.NewDecoder(bytes.NewBuffer(src))
  if err := dec.Decode(dst); err != nil {
    return cookieError{cause: err, typ: decodeError}
  }
  return nil
}
func (e JSONEncoder) Serialize(src interface{}) ([]byte, error) {
  buf := new(bytes.Buffer)
  enc := json.NewEncoder(buf)
  if err := enc.Encode(src); err != nil {
    return nil, cookieError{cause: err, typ: usageError}
  }
  return buf.Bytes(), nil
}
func (e JSONEncoder) Deserialize(src []byte, dst interface{}) error {
  dec := json.NewDecoder(bytes.NewReader(src))
  if err := dec.Decode(dst); err != nil {
    return cookieError{cause: err, typ: decodeError}
  }
  return nil
}

我们可以调用securecookie.SetSerializer(JSONEncoder{})设置使用 JSON 编码:

var (
  hashKey = securecookie.GenerateRandomKey(16)
  blockKey = securecookie.GenerateRandomKey(16)
  s = securecookie.New(hashKey, blockKey)
)
func init() {
  s.SetSerializer(securecookie.JSONEncoder{})
}

自定义编解码

我们可以定义一个类型实现Serializer接口,那么该类型的对象可以用作securecookie的编解码器。我们实现一个简单的 XML 编解码器:

package main
type XMLEncoder struct{}
func (x XMLEncoder) Serialize(src interface{}) ([]byte, error) {
  buf := &bytes.Buffer{}
  encoder := xml.NewEncoder(buf)
  if err := encoder.Encode(buf); err != nil {
    return nil, err
  }
  return buf.Bytes(), nil
}
func (x XMLEncoder) Deserialize(src []byte, dst interface{}) error {
  dec := xml.NewDecoder(bytes.NewBuffer(src))
  if err := dec.Decode(dst); err != nil {
    return err
  }
  return nil
}
func init() {
  s.SetSerializer(XMLEncoder{})
}

由于securecookie.cookieError未导出,XMLEncoderGobEncoder/JSONEncoder返回的错误有些不一致,不过不影响使用。

Hash/Block 函数

securecookie默认使用sha256.New作为 Hash 函数(用于 HMAC 算法),使用aes.NewCipher作为 Block 函数(用于加解密):

// securecookie.go
func New(hashKey, blockKey []byte) *SecureCookie {
  s := &SecureCookie{
    hashKey:   hashKey,
    blockKey:  blockKey,
    // 这里设置 Hash 函数
    hashFunc:  sha256.New,
    maxAge:    86400 * 30,
    maxLength: 4096,
    sz:        GobEncoder{},
  }
  if hashKey == nil {
    s.err = errHashKeyNotSet
  }
  if blockKey != nil {
    // 这里设置 Block 函数
    s.BlockFunc(aes.NewCipher)
  }
  return s
}

可以通过securecookie.HashFunc()修改 Hash 函数,传入一个func () hash.Hash类型:

func (s *SecureCookie) HashFunc(f func() hash.Hash) *SecureCookie {
  s.hashFunc = f
  return s
}

通过securecookie.BlockFunc()修改 Block 函数,传入一个f func([]byte) (cipher.Block, error)

func (s *SecureCookie) BlockFunc(f func([]byte) (cipher.Block, error)) *SecureCookie {
  if s.blockKey == nil {
    s.err = errBlockKeyNotSet
  } else if block, err := f(s.blockKey); err == nil {
    s.block = block
  } else {
    s.err = cookieError{cause: err, typ: usageError}
  }
  return s
}

更换这两个函数更多的是处于安全性的考虑,例如选用更安全的sha512算法:

s.HashFunc(sha512.New512_256)

更换 Key

为了防止 cookie 泄露造成安全风险,有个常用的安全策略:定期更换 Key。更换 Key,让之前获得的 cookie 失效。对应securecookie库,就是更换SecureCookie对象:

var (
  prevCookie    unsafe.Pointer
  currentCookie unsafe.Pointer
)
func init() {
  prevCookie = unsafe.Pointer(securecookie.New(
    securecookie.GenerateRandomKey(64),
    securecookie.GenerateRandomKey(32),
  ))
  currentCookie = unsafe.Pointer(securecookie.New(
    securecookie.GenerateRandomKey(64),
    securecookie.GenerateRandomKey(32),
  ))
}

程序启动时,我们先生成两个SecureCookie对象,然后每隔一段时间就生成一个新的对象替换旧的。

由于每个请求都是在一个独立的 goroutine 中处理的(读),更换 key 也是在一个单独的 goroutine(写)。为了并发安全,我们必须增加同步措施。但是这种情况下使用锁又太重了,毕竟这里更新的频率很低。

我这里将securecookie.SecureCookie对象存储为unsafe.Pointer类型,然后就可以使用atomic原子操作来同步读取和更新了:

func rotateKey() {
  newcookie := securecookie.New(
    securecookie.GenerateRandomKey(64),
    securecookie.GenerateRandomKey(32),
  )
  atomic.StorePointer(&prevCookie, currentCookie)
  atomic.StorePointer(&currentCookie, unsafe.Pointer(newcookie))
}

rotateKey()需要在一个新的 goroutine 中定期调用,我们在main函数中启动这个 goroutine

func main() {
  ctx, cancel := context.WithCancel(context.Background())
  defer cancel()
  go RotateKey(ctx)
}
func RotateKey(ctx context.Context) {
  ticker := time.NewTicker(30 * time.Second)
  defer ticker.Stop()
  for {
    select {
    case <-ctx.Done():
      break
    case <-ticker.C:
    }
    rotateKey()
  }
}

这里为了方便测试,我设置每隔 30s 就轮换一次。同时为了防止 goroutine 泄漏,我们传入了一个可取消的Context。还需要注意time.NewTicker()创建的*time.Ticker对象不使用时需要手动调用Stop()关闭,否则会造成资源泄漏。

使用两个SecureCookie对象之后,我们编解码可以调用EncodeMulti/DecodeMulti这组方法,它们可以接受多个SecureCookie对象:

func SetCookieHandler(w http.ResponseWriter, r *http.Request) {
  u := &User{
    Name: "dj",
    Age:  18,
  }
  if encoded, err := securecookie.EncodeMulti(
    "user", u,
    // 看这里
    (*securecookie.SecureCookie)(atomic.LoadPointer(&currentCookie)),
  ); err == nil {
    cookie := &http.Cookie{
      Name:     "user",
      Value:    encoded,
      Path:     "/",
      Secure:   true,
      HttpOnly: true,
    }
    http.SetCookie(w, cookie)
  }
  fmt.Fprintln(w, "Hello World")
}

使用unsafe.Pointer保存SecureCookie对象后,使用时需要类型转换。并且由于并发问题,需要使用atomic.LoadPointer()访问。

解码时调用DecodeMulti依次传入currentCookieprevCookie,让prevCookie不会立刻失效:

func ReadCookieHandler(w http.ResponseWriter, r *http.Request) {
  if cookie, err := r.Cookie("user"); err == nil {
    u := &User{}
    if err = securecookie.DecodeMulti(
      "user", cookie.Value, u,
      // 看这里
      (*securecookie.SecureCookie)(atomic.LoadPointer(&currentCookie)),
      (*securecookie.SecureCookie)(atomic.LoadPointer(&prevCookie)),
    ); err == nil {
      fmt.Fprintf(w, "name:%s age:%d", u.Name, u.Age)
    } else {
      fmt.Fprintf(w, "read cookie error:%v", err)
    }
  }
}

运行程序:

$ go run main.go

先请求localhost:8080/set_cookie,然后请求localhost:8080/read_cookie读取 cookie。等待 1 分钟后,再次请求,发现之前的 cookie 失效了:

read cookie error:securecookie: the value is not valid (and 1 other error)

总结

securecookie为 cookie 添加了一层保护罩,让 cookie 不能轻易地被读取和伪造。还是需要强调一下:

敏感数据不要放在 cookie 中!敏感数据不要放在 cookie 中!敏感数据不要放在 cookie 中!敏感数据不要放在 cookie 中!

重要的事情说 4 遍。在使用 cookie 存放数据时需要仔细权衡。

大家如果发现好玩、好用的 Go 语言库,欢迎到 Go 每日一库 GitHub 上提交 issue

参考

gorilla/securecookie GitHub:github.com/gorilla/securecookie

Go 每日一库 GitHub:https://github.com/darjun/go-daily-lib

以上就是Go gorilla securecookie库的安装使用详解的详细内容,更多关于Go gorilla securecookie库的资料请关注我们其它相关文章!

(0)

相关推荐

  • go日志库logrus的安装及快速使用

    目录 安装简介 快速使用 支持的日志级别 日期 打印调用位置 添加字段 给字段值加引号 设置钩子 设置channel 输出日志 安装简介 Logrus是Go的结构化日志记录器,与标准的日志记录器库完全API兼容. go get安装的logrus库 go get github.com/sirupsen/logrus 快速使用 package main import ( log "github.com/sirupsen/logrus" ) func main() { log.SetLeve

  • Golang标准库unsafe源码解读

    目录 引言 unsafe包 unsafe构成 type ArbitraryType int type Pointer *ArbitraryType 灵活转换 潜在的危险性 正确的使用姿势 错误的使用姿势 func Sizeof(x ArbitraryType) uintptr func Offsetof(x ArbitraryType) uintptr func Alignof(x ArbitraryType) uintptr 引言 当你阅读Golang源码时一定遇到过unsafe.Pointe

  • Go 类型转化工具库cast函数详解

    目录 1. cast是啥 2. 两种API 3. 源码分析 1. cast是啥 cast 是在Github上开源的工具库,就像他的名字一样,他为我们提供了非常便捷的类型转化的方法. 我们可以通过下面的地址拉取cast库: go get github.com/spf13/cast 2. 两种API cast库中为我们提供了两类常用的API:cast.Toxxx以及cast.ToxxxE(xxx是要转化成的数据类型). cast.ToxxxE在返回转化后数值的同时,也会返回一个error,cast.

  • golang图片处理库image基本操作

    目录 基本操作 读取 新建 保存 图片修改 转换 裁剪 缩放 基本操作 图片的基本读取与保存. 读取 图片读取和文件读取类似,需要先获取流: 注册图片的解码器(如:jpg则import _ "image/jpeg", png则import _ "image/png") 通过os.open打开文件获取流: 通过image.Decode解码流,获取图片: import _ "image/jpeg" func readPic() image.Image

  • golang实现文件上传并转存数据库功能

    本文实例为大家分享了golang实现文件上传并转存数据库的具体代码,供大家参考,具体内容如下 需求 上传图片,且可选择将图片保存到数据中. 一.流程图 二.步骤 1.上传文件接口 获取文件,并返回base64string流 代码如下(示例): func setIconPost(c *gin.Context)  {     //获取文件,icon实现对上传文件的访问,header是对上传文件信息的标记     icon,header,err :=c.Request.FormFile("file&q

  • go语言标准库fmt包的一键入门

    目录 ️ 实战场景 Print 系列函数 Fprint 函数 Sprint 函数 Errorf 函数 ️ 格式化占位符 通用部分 整型相关 浮点数与复数 布尔型和指针 ️ 标准输入 fmt.Scan fmt.Scanf fmt.Scanln Fscan 和 Sscan 系列函数 ️ 实战场景 本篇博客为大家带来 Go 语言中的 fmt 包,该包实现了标准输入和输出两大部分内容. 首先对外输出,包含 Print,Fprint,Sprint,Errorof 等内容,其中 Print 在之前的博客中已

  • Go gorilla securecookie库的安装使用详解

    目录 简介 快速使用 使用 JSON 自定义编解码 Hash/Block 函数 更换 Key 总结 简介 cookie 是用于在 Web 客户端(一般是浏览器)和服务器之间传输少量数据的一种机制.由服务器生成,发送到客户端保存,客户端后续的每次请求都会将 cookie 带上.cookie 现在已经被多多少少地滥用了.很多公司使用 cookie 来收集用户信息.投放广告等. cookie 有两大缺点: 每次请求都需要传输,故不能用来存放大量数据: 安全性较低,通过浏览器工具,很容易看到由网站服务器

  • C++ boost库的安装过程详解

    Windows安装boost库 下载链接:https://www.boost.org/ 学习链接:https://theboostcpplibraries.com/ 1,下载解压,我的目录"C:\Program Files (x86)\Microsoft Visual Studio\2017" 2,以管理员身份运行"适用于 VS 2017 的 x64 本机工具命令提示" 3,执行以下命令进行编译: cd /d "C:\Program Files (x86)

  • Winserver2012下mysql 5.7解压版(zip)配置安装教程详解

    一.安装 1.下载mysql zip版本mysql不需要运行可执行文件,解压即可,下载zip版本mysql msi版本mysql双击文件即可安装,相对简单,本文不介绍此版本安装 2.配置环境变量 打开环境变量配置页面(winserver服务器环境变量位置:服务器管理器->本地服务器->计算机名称->高级->环境变量),在系统变量path后面添加mysql bin文件路径,例如:;C:\mysql-5.7.17-winx64\bin 3.配置mysql mysql配置文件my-def

  • MySQL 5.6.36 Windows x64位版本的安装教程详解

    1,目标环境 Windows 7 64位 2,材料 (1)VC++2010 发布包(64位) (2)MySQL 5.6.36 Windows x64位版本(非MSI,可从官网获取免费版本) (3)EditPlus(可选) 3,基础操作 本文中部分操作需以管理员身份+命令行进行执行. 4,步骤 (1)(解压到当前文件夹方式)解压安装包,编辑其中的my-default.ini文件,主要是2项: ①basedir即为mysql基础文件夹,形如:C:\mysql-5.6.36-winx64 ②datad

  • 基于python中pygame模块的Linux下安装过程(详解)

    一.使用pip安装Python包 大多数较新的Python版本都自带pip,因此首先可检查系统是否已经安装了pip.在Python3中,pip有时被称为pip3. 1.在Linux和OS X系统中检查是否安装了pip 打开一个终端窗口,并执行如下命令: Python2.7中: zhuzhu@zhuzhu-K53SJ:~$ pip --version pip 8.1.1 from /usr/lib/python2.7/dist-packages (python 2.7) Python3.X中: z

  • python3.5安装python3-tk详解

    在python3.5下安装好matplotlib后,准备显示一张图片测试一下,但是控制台报错说需要安装python3-tk,我天真的以为直接: sudo apt-get install python3-tk 就可以了呢.但是不行,说是找不到对应的资源.我就开始各种百度,谷歌,网上各种帖子,依然没有解决我的问题.后来找到一个python3-tk的安装包,deb格式的.我以为这样就行了呢,开始执行: sudo dpkg -i .....deb 发现它依赖blt,这是什么鬼,不管了,先安装再说.这又各

  • Postman的下载及安装教程详解

    Postman背景介绍 用户在开发或者调试网络程序或者是网页B/S模式的程序的时候是需要一些方法来跟踪网页请求的,用户可以使用一些网络的监视工具比如著名的Firebug等网页调试工具.今天给大家介绍的这款网页调试工具不仅可以调试简单的css.html.脚本等简单的网页基本信息,它还可以发送几乎所有类型的HTTP请求!Postman在发送网络HTTP请求方面可以说是Chrome插件类产品中的代表产品之一. Postman的操作环境 postman适用于不同的操作系统,Postman Mac.Win

  • 对linux下软件(库)的更新命令详解

    在ubuntu服务器下安装包的时候,经常会用到sudo apt-get install 包名 或 sudo pip install 包名,那么两者有什么区别呢? 1.区别 pip用来安装来自PyPI(https://www.python.org/)的python所有的依赖包,并且可以选择安装任何在PyPI上已上传的先前版本的依赖包,个人认为是python相关的包和第三方包以及各种版本: apt-get可以用来安装软件.更新源.也可以用来更新自Ubuntu(https://launchpad.ne

  • 对Python中gensim库word2vec的使用详解

    pip install gensim安装好库后,即可导入使用: 1.训练模型定义 from gensim.models import Word2Vec model = Word2Vec(sentences, sg=1, size=100, window=5, min_count=5, negative=3, sample=0.001, hs=1, workers=4) 参数解释: 1.sg=1是skip-gram算法,对低频词敏感:默认sg=0为CBOW算法. 2.size是输出词向量的维数,值

  • Python的pygame安装教程详解

    简介 关于Pygame的基本信息,pygame是什么,谁会被Pygame吸引,并且在哪里找到它. Pygame是被设计用来写游戏的python模块集合,Pygame是在优秀的SDL库之上开发的功能性包.使用python可以导入pygame来开发具有全部特性的游戏和多媒体软件,Pygame是极度轻便的并且可以运行在几乎所有的平台和操作系统上.Pygame包已经被下载过成千上万次,并且也被访问过成千上万次. Pygame是免费的,发行遵守GPL,你可以利用它开发开源的.免费的.免费软件.共享件.还有

随机推荐