您好,登錄后才能下訂單哦!
本文小編為大家詳細(xì)介紹“Go gorilla securecookie庫怎么安裝使用”,內(nèi)容詳細(xì),步驟清晰,細(xì)節(jié)處理妥當(dāng),希望這篇“Go gorilla securecookie庫怎么安裝使用”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學(xué)習(xí)新知識(shí)吧。
cookie 是用于在 Web 客戶端(一般是瀏覽器)和服務(wù)器之間傳輸少量數(shù)據(jù)的一種機(jī)制。由服務(wù)器生成,發(fā)送到客戶端保存,客戶端后續(xù)的每次請(qǐng)求都會(huì)將 cookie 帶上。cookie 現(xiàn)在已經(jīng)被多多少少地濫用了。很多公司使用 cookie 來收集用戶信息、投放廣告等。
cookie 有兩大缺點(diǎn):
每次請(qǐng)求都需要傳輸,故不能用來存放大量數(shù)據(jù);
安全性較低,通過瀏覽器工具,很容易看到由網(wǎng)站服務(wù)器設(shè)置的 cookie。
gorilla/securecookie提供了一種安全的 cookie,通過在服務(wù)端給 cookie 加密,讓其內(nèi)容不可讀,也不可偽造。當(dāng)然,敏感信息還是強(qiáng)烈建議不要放在 cookie 中。
本文代碼使用 Go Modules。
創(chuàng)建目錄并初始化:
$ 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)) }
首先需要?jiǎng)?chuàng)建一個(gè)SecureCookie
對(duì)象:
var s = securecookie.New(hashKey, blockKey)
其中hashKey
是必填的,它用來驗(yàn)證 cookie 是否是偽造的,底層使用 HMAC(Hash-based message authentication code)算法。推薦hashKey
使用 32/64 字節(jié)的 Key。
blockKey
是可選的,它用來加密 cookie,如不需要加密,可以傳nil
。如果設(shè)置了,它的長(zhǎng)度必須與對(duì)應(yīng)的加密算法的塊大小(block size)一致。例如對(duì)于 AES 系列算法,AES-128/AES-192/AES-256 對(duì)應(yīng)的塊大小分別為 16/24/32 字節(jié)。
為了方便也可以使用GenerateRandomKey()
函數(shù)生成一個(gè)安全性足夠強(qiáng)的隨機(jī) key。每次調(diào)用該函數(shù)都會(huì)返回不同的 key。上面代碼就是通過這種方式創(chuàng)建 key 的。
調(diào)用s.Encode("user", u)
將對(duì)象u
編碼成字符串,內(nèi)部實(shí)際上使用了標(biāo)準(zhǔn)庫encoding/gob
。所以gob
支持的類型都可以編碼。
調(diào)用s.Decode("user", cookie.Value, u)
將 cookie 值解碼到對(duì)應(yīng)的u
對(duì)象中。
運(yùn)行:
$ go run main.go
首先使用瀏覽器訪問localhost:8080/set_cookie
,這時(shí)可以在 Chrome 開發(fā)者工具的 Application 頁簽中看到 cookie 內(nèi)容:
訪問localhost:8080/read_cookie
,頁面顯示name: dj age: 18
。
securecookie
默認(rèn)使用encoding/gob
編碼 cookie 值,我們也可以改用encoding/json
。securecookie
將編解碼器封裝成一個(gè)Serializer
接口:
type Serializer interface { Serialize(src interface{}) ([]byte, error) Deserialize(src []byte, dst interface{}) error }
securecookie
提供了GobEncoder
和JSONEncoder
的實(shí)現(xiàn):
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 }
我們可以調(diào)用securecookie.SetSerializer(JSONEncoder{})
設(shè)置使用 JSON 編碼:
var ( hashKey = securecookie.GenerateRandomKey(16) blockKey = securecookie.GenerateRandomKey(16) s = securecookie.New(hashKey, blockKey) ) func init() { s.SetSerializer(securecookie.JSONEncoder{}) }
我們可以定義一個(gè)類型實(shí)現(xiàn)Serializer
接口,那么該類型的對(duì)象可以用作securecookie
的編解碼器。我們實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 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
未導(dǎo)出,XMLEncoder
與GobEncoder/JSONEncoder
返回的錯(cuò)誤有些不一致,不過不影響使用。
securecookie
默認(rèn)使用sha256.New
作為 Hash 函數(shù)(用于 HMAC 算法),使用aes.NewCipher
作為 Block 函數(shù)(用于加解密):
// securecookie.go func New(hashKey, blockKey []byte) *SecureCookie { s := &SecureCookie{ hashKey: hashKey, blockKey: blockKey, // 這里設(shè)置 Hash 函數(shù) hashFunc: sha256.New, maxAge: 86400 * 30, maxLength: 4096, sz: GobEncoder{}, } if hashKey == nil { s.err = errHashKeyNotSet } if blockKey != nil { // 這里設(shè)置 Block 函數(shù) s.BlockFunc(aes.NewCipher) } return s }
可以通過securecookie.HashFunc()
修改 Hash 函數(shù),傳入一個(gè)func () hash.Hash
類型:
func (s *SecureCookie) HashFunc(f func() hash.Hash) *SecureCookie { s.hashFunc = f return s }
通過securecookie.BlockFunc()
修改 Block 函數(shù),傳入一個(gè)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 }
更換這兩個(gè)函數(shù)更多的是處于安全性的考慮,例如選用更安全的sha512
算法:
s.HashFunc(sha512.New512_256)
為了防止 cookie 泄露造成安全風(fēng)險(xiǎn),有個(gè)常用的安全策略:定期更換 Key。更換 Key,讓之前獲得的 cookie 失效。對(duì)應(yīng)securecookie
庫,就是更換SecureCookie
對(duì)象:
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), )) }
程序啟動(dòng)時(shí),我們先生成兩個(gè)SecureCookie
對(duì)象,然后每隔一段時(shí)間就生成一個(gè)新的對(duì)象替換舊的。
由于每個(gè)請(qǐng)求都是在一個(gè)獨(dú)立的 goroutine 中處理的(讀),更換 key 也是在一個(gè)單獨(dú)的 goroutine(寫)。為了并發(fā)安全,我們必須增加同步措施。但是這種情況下使用鎖又太重了,畢竟這里更新的頻率很低。
我這里將securecookie.SecureCookie
對(duì)象存儲(chǔ)為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()
需要在一個(gè)新的 goroutine 中定期調(diào)用,我們?cè)?code>main函數(shù)中啟動(dòng)這個(gè) 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() } }
這里為了方便測(cè)試,我設(shè)置每隔 30s 就輪換一次。同時(shí)為了防止 goroutine 泄漏,我們傳入了一個(gè)可取消的Context
。還需要注意time.NewTicker()
創(chuàng)建的*time.Ticker
對(duì)象不使用時(shí)需要手動(dòng)調(diào)用Stop()
關(guān)閉,否則會(huì)造成資源泄漏。
使用兩個(gè)SecureCookie
對(duì)象之后,我們編解碼可以調(diào)用EncodeMulti/DecodeMulti
這組方法,它們可以接受多個(gè)SecureCookie
對(duì)象:
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(¤tCookie)), ); 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
對(duì)象后,使用時(shí)需要類型轉(zhuǎn)換。并且由于并發(fā)問題,需要使用atomic.LoadPointer()
訪問。
解碼時(shí)調(diào)用DecodeMulti
依次傳入currentCookie
和prevCookie
,讓prevCookie
不會(huì)立刻失效:
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(¤tCookie)), (*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) } } }
運(yùn)行程序:
$ go run main.go
先請(qǐng)求localhost:8080/set_cookie
,然后請(qǐng)求localhost:8080/read_cookie
讀取 cookie。等待 1 分鐘后,再次請(qǐng)求,發(fā)現(xiàn)之前的 cookie 失效了:
read cookie error:securecookie: the value is not valid (and 1 other error)
讀到這里,這篇“Go gorilla securecookie庫怎么安裝使用”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識(shí)點(diǎn)還需要大家自己動(dòng)手實(shí)踐使用過才能領(lǐng)會(huì),如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。