前言
本文主要介紹 go開源框架gin 是如何使用session的。以及前端 vue3 + [email protected] 是如何配合session一起使用的
主要介紹內容包括這些:
- session的基本原理
- gin配置session
- axios 如何設置攜帶cookie (chrome版本 < 80)
- axios 如何在跨域請求時攜帶cookie(chrome版本 < 80)
- chrome瀏覽器版本 >= 80 時,axios 不攜帶cookie的解決方案
- session的一些基本配置操作
- session的Flashes講解
正文
session的基本原理
http協議是無狀態的,就是說你在請求服務器同時,張三也在請求服務器,這時候服務器是不知道哪個請求是你,哪個請求張三(除非你們攜帶了一些識別字段,不過這屬於業務層內容,不在討論範圍,我們單說http協議)。
爲了讓服務器知道哪個請求時你的,哪個是張三的。這時候就有了session。
session和cookie是不分家的。我們每次說到session,其實默認就是要使用cookie了。
我們舉例說明一下什麼是session:
你要去逛澡堂,剛進門來到服務檯時你是沒有開儲物櫃的鑰匙的。當你交錢了,服務檯就給你一把儲物櫃的鑰匙,這時儲物櫃就和你的鑰匙綁定了。你去游泳、搓澡,你一直會把鑰匙帶在身上。這時候你不管什麼時候去服務檯,只要你拿着鑰匙,就會認識你,你拿着鑰匙可以隨便的到儲物櫃中拿取自己的東西。你要離開時,把鑰匙櫃服務檯就可以了。
在程序裏是怎麼實現上面的過程的呢?
服務器就是服務檯,儲物櫃就是session倉庫,鑰匙就是我們的憑證。攜帶鑰匙其實就是cookie傳輸。
第一次登錄,服務器給客戶端頒發一個唯一的sessionId, 並通過http的響應頭返回。客戶端(瀏覽器)發現返回的數據中有cookie數據就把這個cookie數據存放到內存。下次再發送http請求時,把內存中的cookie數據再塞到http請求頭中,一併發給服務器,服務器在解析請求時,發現請求頭中有cookie,就開始識別cookie中的sessionId,拿到sessionId,我們就知道這個請求時由哪個客戶端發送來的了。
gin配置session
gin框架在處理session時有專門的中間件,我們可以直接使用。
中間件: github.com/gin-contrib/sessions
, 我們直接安裝依賴: go get github.com/gin-contrib/sessions
即可引入使用
sessions使用教程
我們參考官網案例:
package main
import (
// 導入session包
"github.com/gin-contrib/sessions"
// 導入session存儲引擎
"github.com/gin-contrib/sessions/cookie"
// 導入gin框架包
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// 創建基於cookie的存儲引擎,shuiche 參數是用於加密的密鑰,可以隨便填寫
store := cookie.NewStore([]byte("shuiche"))
// 設置session中間件,參數mysession,指的是session的名字,也是cookie的名字
// store是前面創建的存儲引擎
r.Use(sessions.Sessions("mysession", store))
r.GET("/test", func(c *gin.Context) {
// 初始化session對象
session := sessions.Default(c)
// 通過session.Get讀取session值
// session是鍵值對格式數據,因此需要通過key查詢數據
if session.Get("hello") != "world" {
// 設置session數據,()
session.Set("hello", "world")
// 刪除session數據
session.Delete("tizi365")
// 保存session數據
session.Save()
// 刪除整個session
// session.Clear()
}
c.JSON(200, gin.H{"hello": session.Get("hello")})
})
r.Run(":8000")
}
使用sessions 中間件注意要點:
- session 倉庫其實就是一個 map[interface]interface 對象,所有 session可以存儲任意數據
- session 使用的編解碼器是自帶的gob,所以存儲類似: struct、map 這些對象時需要先註冊對象,不然會報錯
gob: type not registered for...
- session 存儲引擎支持: cookie、內存、mongodb、redis、postgres、memstore、memcached 以及 gorm 支持的各類數據庫(mysql、sqlite)
- session 在創建時有一個配置項,可以配置session過期時間、cookie、domain、secure、path等參數
- 調用 session 方法: Set()、 Delete()、 Clear()、方法後,必須調用一次 Save() 方法。否則session數據不會更新
gob註冊案例
type User struct{
Name string
}
gob.Register(User{})
session配置項案例
// store 就是前面創建的存儲引擎
store.Options(sessions.Options{
Secure: true,
SameSite: 4,
Path: "/",
MaxAge: m.MaxAge,
})
axios 如何設置攜帶cookie (chrome版本 < 80)
axios請求默認是不攜帶 Cookie 的,如果需要攜帶 Cookie 前端需要配置
// 全局配置
axios.defaults.withCredentials = true
// 實例配置
const service = axios.create({
baseURL: process.env.BASE_API,
timeout: 5000,
withCredentials: true // 配置項
})
// 單個路由配置
axios.get('/test', {
withCredentials: true
})
axios 如何在跨域請求時攜帶cookie(chrome版本 < 80)
涉及到跨域,我們就要想到要在服務器端做處理
我們給服務器開啓 CORS 跨域,我們給gin添加 CORS 中間件
跨域配置中間件
func(c *gin.Context) {
method := c.Request.Method
origin := c.GetHeader("Origin")
c.Header("Access-Control-Allow-Origin", origin) // 注意這一行,不能配置爲通配符“*”號
c.Header("Access-Control-Allow-Credentials", "true") // 注意這一行,必須設定爲 true
c.Header("Access-Control-Allow-Headers", "Access-Control-Allow-Headers,Cookie, Origin, X-Requested-With, Content-Type, Accept, Authorization, Token, Timestamp, UserId") // 我們自定義的header字段都需要在這裏聲明
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS,DELETE,PUT")
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type,cache-control")
// 放行所有OPTIONS方法
if method == "OPTIONS" {
//c.AbortWithStatus(http.StatusNoContent)
c.AbortWithStatus(http.StatusOK)
}
// 處理請求
c.Next()
}
chrome瀏覽器版本 >= 80 時,axios 不攜帶cookie的解決方案
如果我們的chrome瀏覽器版本高於80,我們會發現按照上面兩步配置的cookie還是無法生效,這是因爲chrome爲了增加安全性修改了攜帶cookie的方式。
這裏分兩種情況講解:
線上情況
- 設置: samesite: none,也就是session的store在設定options時設定的字段配置爲:SameSite: 4,(這裏4 就代表none)。
- secure 配置爲 true
- 服務端開啓https (必須)
1、2 配置案例查看 上面的 session配置項案例
開發環境
用127.0.0.1地址或者localhost地址測試
優化方案
我們可以利用cookie機制發送sessionId。目前我正在考慮使用自定義header字段攜帶sessionId,基本邏輯:
前端:請求返回後,判斷是否有我們命名的session 名稱的cookie,(就是創建session時的自定義名稱:mysession),如果有將值存儲到內存。 發送請求時,將存儲的session值添加到http請求的header中。
服務端:將校驗session的中間件中的獲取session 修改爲從header中獲取。
因爲chrome>80的瀏覽器是發不出去cookie,不是收不到cookie。我們就不用瀏覽器的字段攜帶cookie機制,搞成手動攜帶cookie
session的一些基本配置操作
設置session過期時間
設置:store.Options(sessions.Options{MaxAge: 86400*30})
session 刷新超時時間
基本思路:
每次獲取到請求後, 我們將該用戶的session重新賦值,並返回給客戶端,客戶端下次請求時攜帶新的session請求。
當前 session 用戶數量
有時候我們系統需要統計當前在線用戶數量,這是我們就不能用cookie或者內存存儲session了,需要用數據庫存儲。
我們把session存儲到數據庫後,通過查詢當前沒過期session數量就可以確定在線用戶數
註銷session
我們在退出登錄時需要刪掉session,只需要調用 sesssion的 Delete()方法即可, 記得調用 Save方法保存操作
vue3 瀏覽器在關閉時發送退出登錄請求
在app.vue 文件中添加這樣的代碼, 其中:gapTime <= 12
中的 12 是經驗值。vue文件使用 setup語法糖模式
onMounted(() => {
window.addEventListener('beforeunload', beforeunloadHandler)
window.addEventListener('unload', unloadHandler)
})
function beforeunloadHandler (e) {
beforeUnloadTime = new Date().getTime()
}
function unloadHandler (e) {
gapTime = new Date().getTime() - beforeUnloadTime
// 判斷是窗口關閉還是刷新
console.log('====gapTime=======', gapTime)
if (gapTime <= 12) {
// 如果是登錄狀態,關閉窗口前,移除用戶
navigator.sendBeacon(`/user/logout`, data) // 這裏data是請求的參數。一個字符串,具體查看navigator.sendBeacon 使用方法
}
debugger
}
session的Flashes講解
我們在看 session庫 源碼時會發現還有兩個函數:Flashes()
和 AddFlash
。簡單說一下這兩個函數作用
Flash既閃存,不是我們瀏覽器裏的那個flash插件,flash主要是存儲一下臨時數據。
- 我們調用 AddFlash時,會往flash裏存儲 一個鍵值對。
- 當我們調用 Flashs 讀取了 閃存內容後,這個閃存數據就被刪除了。(記得調用 Save()方法生效)
後記
本篇博文耗費了比較大的精力才研究透徹,如果對你有幫助,可以點個推薦,當然想請我喝杯可樂我也會很開心的哦