基於cookie的用戶登錄狀態管理


cookie-image

cookie是什么

先來花5分鍾看完這篇文章:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Cookies

看完上文,相信大家對cookie已經有了一個整體的概念,我再強調一下,cookie是一個客戶端概念,它是存儲在瀏覽器本地的一小段文本(通常由服務器來生成這段文本)。

cookie的作用

如上文所說,cookie有許多作用,如會話狀態管理,個性化設置,瀏覽器行為跟蹤,客戶端數據的存儲等等。本篇文章就來講講基於cookie的用戶登錄狀態管理。

插一句哈,一般提到cookie,還會有一個叫session的家伙和它一起出現,下篇文章我會講到它,以及兩者的區別。

cookie的產生過程

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": "退出成功"})
}

我們來看具體的演示流程和效果:

login

me

logout

如下圖所示,當我們退出后再去嘗試訪問個人頁面時,會出現401沒有權限的錯誤。

noauth

上述例子的缺點

先來說說上面的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。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM