Cookie
(也叫Web Cookie
或瀏覽器Cookie
)是服務器發送到用戶瀏覽器並保存在本地的一小塊數據,它會在瀏覽器下次向同一服務器再發起請求時被攜帶併發送到服務器上。一般,它用於告知服務端兩個請求是否來自同一瀏覽器,如保持用戶的登陸狀態。Cookie
使基於無狀態的HTTP協議記錄穩定的狀態信息成爲了可能。算法
Cookie
主要用於如下三個方面:編程
在Go
的net/http
庫中使用http.Cookie
結構體表示一個Cookie
數據,調用http.SetCookie
函數則會告訴終端用戶的瀏覽器把給定的http.Cookie
值設置到瀏覽器Cookie
裏,相似下面:跨域
func someHandler(w http.ResponseWriter, r *http.Request) { c := http.Cookie{ Name: "UserName", Value: "Casey", } http.SetCookie(w, &c) }
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 }
Name
和Value
字段就很少說了,單獨針對幾個須要解釋的字段進行說明。安全
默認值是當前正在訪問的Host
的域名,假設咱們如今正在訪問的是www.example.com
,若是須要其餘子域名也可以訪問到正在設置的Cookie
值的話,將它設置爲example.com
。注意,只有正在被設置的Cookie
須要被其餘子域名的服務訪問到時才這麼設置。服務器
c := Cookie{ ...... Domain: "example.com", }
設置當前的 Cookie 值只有在訪問指定路徑時才能被服務器程序讀取。默認爲服務端應用程序上的任何路徑,可是您可使用它限制爲特定的子目錄。例如:cookie
c := Cookie{ Path: "/app/", }
標記爲Secure
的Cookie只應經過被HTTPS
協議加密過的請求發送給服務端。但即使設置了 Secure
標記,敏感信息也不該該經過Cookie
傳輸,由於Cookie
有其固有的不安全性,Secure
標記也沒法提供確實的安全保障。從 Chrome 52 和 Firefox 52 開始,不安全的站點(http:
)沒法使用Cookie
的 Secure
標記。session
爲避免跨域腳本 (XSS) 攻擊,經過JavaScript
的API沒法訪問帶有 HttpOnly
標記的Cookie,它們只應該發送給服務端。若是包含服務端Session
信息的Cookie
不想被客戶端JavaScript
腳本調用,那麼就應該爲其設置 HttpOnly
標記。併發
接下來咱們探討兩種安全傳輸Cookie
的方法app
對數據進行數字簽名是在數據上添加「簽名」的行爲,以即可以驗證其真實性。不須要對數據進行加密或屏蔽。
簽名的工做方式是經過散列-咱們對數據進行散列,而後將數據與數據散列一塊兒存儲在Cookie
中。而後,當用戶將Cookie
發送給咱們時,咱們再次對數據進行哈希處理,並驗證其是否與咱們建立的原始哈希匹配。
咱們不但願用戶也用篡改後的數據建立新的哈希,所以常常會看到使用HMAC
之類的哈希算法,以即可以使用密鑰對數據進行哈希。這樣能夠防止最終用戶同時編輯數據和數字簽名(哈希)。
JWT
也是使用的這種數字簽名的方式進行傳輸的。
上面的數據簽名過程並不須要咱們本身去實現,咱們能夠在Go
中使用gorilla/securecookie
的程序包來完成此操做,在該程序包中,你能夠在建立SecureCookie
時爲其提供哈希密鑰,而後使用該對象來保護你的Cookie
。
Cookie
數據進行簽名://var s = securecookie.New(hashKey, blockKey) var hashKey = securecookie.GenerateRandomKey(64) var s = securecookie.New(hashKey, nil) func SetCookieHandler(w http.ResponseWriter, r *http.Request) { encoded, err := s.Encode("cookie-name", "cookie-value") if err == nil { cookie := &http.Cookie{ Name: "cookie-name", Value: encoded, Path: "/", } http.SetCookie(w, cookie) fmt.Fprintln(w, encoded) }
func ReadCookieHandler(w http.ResponseWriter, r *http.Request) { if cookie, err := r.Cookie("cookie-name"); err == nil { var value string if err = s.Decode("cookie-name", cookie.Value, &value); err == nil { fmt.Fprintln(w, value) } } }
注意這裏的Cookie
數據未加密,僅僅是被編碼了,任何人均可以把Cookie
數據解碼回來。
每當將數據存儲在Cookie
中時,請始終儘可能減小存儲在Cookie
中的敏感數據量。不要存儲用戶密碼之類的東西,並確保任何編碼數據也沒有此信息。在某些狀況下,開發人員在不知不覺中將敏感數據存儲在Cookie
或JWT
中,由於它們是base64
編碼的,但實際上任何人均可以解碼該數據。它已編碼,未加密。
這是一個很大的錯誤,所以,若是你擔憂意外存儲敏感內容,建議 你使用gorilla/securecookie
之類的軟件包。
以前咱們討論瞭如何將其用於對Cookie
進行數字簽名,可是securecookie
也能夠用於加密和解密Cookie
數據,以使其沒法輕鬆解碼和讀取。
要使用該軟件包加密Cookie
,只需在建立SecureCookie
實例時傳入一個blockKey
便可。
將上面簽名Cookie
的代碼片斷進行一些小改動,其餘地方徹底不用動,securecookie
包會幫助咱們進行Cookie
的加密和解密:
var hashKey = securecookie.GenerateRandomKey(64) var blockKey = securecookie.GenerateRandomKey(32) var s = securecookie.New(hashKey, blockKey)
今天的文章除了闡述如何使用Go
語言安全地傳輸Cookie
數據外,再次格外強調一遍,編碼和加密的不一樣,從數據可讀性上看,二者差很少,但本質上是徹底不同的:
咱們在作數據傳輸時必定要記住二者的區別,某種意義上,我以爲記住這兩點的區別比你學會今天文章裏怎麼安全傳輸Cookie
更重要。
前文回顧:
使用gorilla/mux加強Go HTTP服務器的路由能力