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

cookie是什麼

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

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

cookie的做用

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

插一句哈,通常提到cookie,還會有一個叫session的傢伙和它一塊兒出現,下篇文章我會講到它,以及二者的區別。web

cookie的產生過程

cookie產生過程

如上圖所示,客戶端攜帶帳號和密碼向服務器發起請求,服務器在校驗經過後,經過HTTP Respose Header中的Set-Cookie頭部,將一小段文本寫入客戶端瀏覽器,在之後的每一個客戶端HTTP Request Header的Cookie頭部中會自動攜帶這段文本。數據庫

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

下面我基於golang和gin框架(中間件使用比較舒服)來簡單的實現一個基於cookie的用戶登陸狀態管理demojson

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確無能爲力,也就是說這段cookie永遠有效。(後面咱們會講一種叫json web token的技術,能夠作到讓咱們簽發的憑證自帶過時機制,而不依賴瀏覽器)

固然,有同窗會說,咱們能夠在服務器存儲一份有效的cookie列表,在用戶退出登陸後,從有效列表中刪除對應的cookie,這種在服務端維護用戶狀態的機制本質是session的思想,咱們後面會講基於session的用戶登陸狀態管理。

再來講說cookie別的缺點:

跨站請求僞造

固然這個咱們經過設置cookie的屬性爲HttpOnly,來禁止JavaScript讀取cookie值,能夠起到必定的防禦做用。

固然,cookie也是有優勢的,咱們把用戶的登陸狀態保存在客戶端,這樣就不須要每一次去訪問數據庫來檢測用戶是否登陸,減小了系統的IO開銷。

最後

本文但願經過一個不是很完美的demo來說述基於cookie的用戶登陸狀態管理,下期咱們來說講session。

相關文章
相關標籤/搜索