基於cookie的使用者登入狀態管理
cookie是什麼
先來花5分鐘看完這篇文章:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Cookies
看完上文,相信大家對cookie已經有了一個整體的概念,我再強調一下,cookie是一個客戶端概念,它是儲存在瀏覽器本地的一小段文字(通常由伺服器來生成這段文字)。
cookie的作用
如上文所說,cookie有許多作用,如會話狀態管理,個性化設定,瀏覽器行為跟蹤,客戶端資料的儲存等等。本篇文章就來講講基於cookie的使用者登入狀態管理。
插一句哈,一般提到cookie,還會有一個叫session的傢伙和它一起出現,下篇文章我會講到它,以及兩者的區別。
cookie的產生過程
如上圖所示,客戶端攜帶賬號和密碼向伺服器發起請求,伺服器在校驗通過後,通過HTTP Respose Header中的Set-Cookie頭部,將一小段文字寫入客戶端瀏覽器,在以後的每個客戶端HTTP Request Header的Cookie頭部中會自動攜帶這段文字。
基於cookie的使用者登入狀態管理
下面我基於golang和gin框架(中介軟體使用比較舒服)來簡單的實現一個基於cookie的使用者登入狀態管理demo
package main import ( "net/http" "time" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() r.GET("/login", Login) // 需要登陸保護的 auth := r.Group("") auth.Use(AuthRequired()) { auth.GET("/me", UserInfo) auth.GET("/logout", Logout) } r.Run("localhost:9000") } // 登陸 func Login(c *gin.Context) { // 為了演示方便,我直接通過url明文傳遞賬號密碼,實際生產中應該用HTTP POST在body中傳遞 userID := c.Query("user_id") password := c.Query("password") // 使用者身份校驗(查詢資料庫) if userID == "007" && password == "007" { // 生成cookie expiration := time.Now() expiration = expiration.AddDate(0, 0, 1) // 實際生產中我們可以加密userID cookie := http.Cookie{Name: "userID", Value: userID, Expires: expiration} http.SetCookie(c.Writer, &cookie) c.JSON(http.StatusOK, gin.H{"msg": "Hello " + userID}) return } c.JSON(http.StatusBadRequest, gin.H{"msg": "賬號或密碼錯誤"}) } // 檢測是否登陸的中介軟體 func AuthRequired() gin.HandlerFunc { return func(c *gin.Context) { cookie, _ := c.Request.Cookie("userID") if cookie == nil { c.JSON(http.StatusUnauthorized, gin.H{"msg": "請先登陸"}) c.Abort() } // 實際生產中應校驗cookie是否合法 c.Next() } } // 檢視使用者個人資訊 func UserInfo(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"msg": "007的個人頁面"}) } // 退出登陸 func Logout(c *gin.Context) { // 設定cookie過期 expiration := time.Now() expiration = expiration.AddDate(0, 0, -1) cookie := http.Cookie{Name: "userID", Value: "", Expires: expiration} http.SetCookie(c.Writer, &cookie) c.JSON(http.StatusOK, gin.H{"msg": "退出成功"}) }
我們來看具體的演示流程和效果:
如下圖所示,當我們退出後再去嘗試訪問個人頁面時,會出現401沒有許可權的錯誤。
上述例子的缺點
先來說說上面的demo存在的問題吧,我們的退出登入函式本質是設定了一個過期了的cookie來覆蓋以前傳送給使用者的正常cookie。
但是,這兒存在著一個重大的安全問題。如果使用者將之前未過期的正常cookie記錄下來(即本例子中的userID=007),即使呼叫了我們的logout介面,只要使用者自己手動輸入之前未過期的正常cookie,也是可以通過伺服器的驗證。
而且,最重要的是,我們無法讓其失效,因為cookie的過期刪除機制是由瀏覽器來控制的,但是當用戶記錄了cookie中的哪段文字後,在cookie到期後,瀏覽器只能刪除存在於瀏覽器中的cookie,對使用者自己記錄下來的cookie確無能為力,也就是說這段cookie永遠有效。(後面我們會講一種叫json web token的技術,可以做到讓我們簽發的憑證自帶過期機制,而不依賴瀏覽器)
當然,有同學會說,我們可以在伺服器儲存一份有效的cookie列表,在使用者退出登入後,從有效列表中刪除對應的cookie,這種在服務端維護使用者狀態的機制本質是session的思想,我們後面會講基於session的使用者登入狀態管理。
再來說說cookie別的缺點:
跨站請求偽造
當然這個我們通過設定cookie的屬性為HttpOnly,來禁止JavaScript讀取cookie值,可以起到一定的防護作用。
當然,cookie也是有優點的,我們把使用者的登入狀態儲存在客戶端,這樣就不需要每一次去訪問資料庫來檢測使用者是否登入,減少了系統的IO開銷。
最後
本文希望通過一個不是很完美的demo來講述基於cookie的使用者登入狀態管理,下期我們來講講session