本文目錄
- 一、模版渲染
- 二、自定義模版函數
- 三、cookie
- 四、Session
- 五、cookie、session區別
- 六、會話攻擊
一、模版渲染
在 Gin 框架中,模板主要用于動態生成 HTML 頁面,結合 Go 語言的模板引擎功能,實現數據與視圖的分離。
模板渲染是一種動態生成 HTML 頁面的技術,通過模板文件和動態數據的結合,Web 應用可以為每個用戶生成不同的內容。Gin 使用 Go 標準庫 html/template 提供模板功能,該模板引擎基于 HTML,能夠避免常見的跨站腳本攻擊 (XSS)。
創建一個template模版文件,其中body
中的.title
就是動態的數據,需要后端請求后顯示。
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>gin_templates</title>
</head>
<body>
{{.title}}
</body>
</html>
寫一個簡單的請求demo來看看模版返回。
package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {r := gin.Default()// 模板解析r.LoadHTMLFiles("./templates/index.tmpl")r.GET("/index", func(ctx *gin.Context) {// 模板的渲染ctx.HTML(http.StatusOK, "index.tmpl", gin.H{"title": "hello,我是模板",})})r.Run(":9090") // 啟動server
}
通過Post請求工具可以看到已經正確加載了模版內容。
如果有多個模版,可以統一進行對應的渲染:
r.LoadHTMLGlob("templates/**")
如果目錄為templates/post/index.tmpl
和templates/user/index.tmpl
這種,可以
// **/* 代表所有子目錄下的所有文件router.LoadHTMLGlob("templates/**/*")
二、自定義模版函數
那么是否可以實現下面的效果呢,答案是可以的,通過自定義模版函數進行處理即可。
在模板渲染中,默認情況下,Go 的模板引擎會對所有輸出的內容進行 HTML 轉義,以防止跨站腳本攻擊(XSS)。例如,如果模板中輸出的內容是
當我們把代碼改為下面時,會發現進行了HTML轉義,并不是我們想要的狀態。
r.GET("/index", func(c *gin.Context) {// HTML請求// 模板的渲染c.HTML(http.StatusOK, "index.tmpl", gin.H{"title": "<a href='http://baidu.com'>跳轉到其他地方</a>",})})
所以現在我們可以添加一個函數,讓這個模版原樣輸出,不再進行html轉義了。(放在加載模版之前即可),并且將index中的代碼改為如下:{{.title | safe }}
。
r.SetFuncMap(template.FuncMap{"safe": func(str string) template.HTML {return template.HTML(str)},})
三、cookie
http是無狀態協議,服務器不能記錄瀏覽器的訪問狀態,也就是說服務器不能區分兩次請求是否由同一個客戶端發出。
Cookie就是解決HTTP協議無狀態的方案之一。
實際上就是服務器保存在瀏覽器上的一段信息,瀏覽器有了Cookie之后,每次向服務器發送請求時都會將信息發送剛給服務器,服務器就可以根據該信息處理請求。
Cookie由服務器創建,發送給瀏覽器,瀏覽器保存。
cookie的函數簽名如下:
func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool)
參數名 | 類型 | 說明 |
---|---|---|
name | string | cookie名字 |
value | string | cookie值 |
maxAge | int | 有效時間,單位是秒,MaxAge=0 忽略MaxAge屬性,MaxAge<0 相當于刪除cookie, 通常可以設置-1代表刪除,MaxAge>0 多少秒后cookie失效 |
path | string | cookie路徑 |
domain | string | cookie作用域 |
secure | bool | Secure=true,那么這個cookie只能用https協議發送給服務器 |
httpOnly | bool | 設置HttpOnly=true的cookie不能被js獲取到 |
r.GET("/cookie", func(c *gin.Context) {// 設置cookiec.SetCookie("site_cookie", "cookievalue", 3600, "/", "localhost", false, true)})
通過post工具可以查到對應的cookie。
通過瀏覽器進入開發者模式也可以查看到對應的cookie。
也可以進行cookie讀取,通過下面代碼。
func main() {r := gin.Default()r.GET("/read", func(c *gin.Context) {// 根據cookie名字讀取cookie值data, err := c.Cookie("site_cookie")if err != nil {// 直接返回cookie值c.String(200, "not found!")return}c.String(200, data)})r.Run(":9090") // 啟動server
}
刪除cookie比較簡單,通過將將cookie的MaxAge設置為-1, 達到刪除cookie的目的。
r.GET("/del", func(c *gin.Context) {// 設置cookie MaxAge設置為-1,表示刪除cookiec.SetCookie("site_cookie", "cookievalue", -1, "/", "localhost", false, true)c.String(200,"刪除cookie")})
四、Session
會話(Session) 是一種用于在 Web 應用中跟蹤用戶狀態的技術。它允許服務器在多個 HTTP 請求之間保持用戶的狀態信息,從而實現諸如用戶登錄、購物車等功能。
當用戶第一次訪問 Web 應用時,服務器會創建一個唯一的會話標識符(Session ID),并將其存儲在服務器端的會話存儲中。
Gin框架中,可以依賴gin-contrib/sessions
中間件處理session。
引入session的依賴。
go install github.com/gin-contrib/sessions@latest
package mainimport ("fmt""github.com/gin-contrib/sessions""github.com/gin-contrib/sessions/cookie""github.com/gin-gonic/gin"
)func main() {r := gin.Default()// 創建基于cookie的存儲引擎,secret 參數是用于加密的密鑰,用于對存儲在 Cookie 中的會話數據進行加密store := cookie.NewStore([]byte("secret"))// sessions.Sessions:注冊會話中間件。這個中間件會自動處理會話的創建、讀取和更新,并將會話數據存儲在客戶端的 Cookie 中。// 設置session中間件,參數mysession,指的是session的名字,也是cookie的名字// store是前面創建的存儲引擎,我們可以替換成其他存儲引擎r.Use(sessions.Sessions("mysession", store))r.GET("/hello", func(c *gin.Context) {// 初始化session對象session := sessions.Default(c)// 通過session.Get讀取session值// session是鍵值對格式數據,因此需要通過key查詢數據if session.Get("hello") != "world" {fmt.Println("沒讀到")// 設置session數據session.Set("hello", "world")session.Save()}c.JSON(200, gin.H{"hello": session.Get("hello")})})r.Run(":8080")
}
通過上面代碼,我們可以驗證session。這里我們是使用cookie進行存儲的,也可以通過redis進行存儲。
通過cookie進行存儲的session是這樣的:當用戶第一次訪問服務器時,服務器會為用戶創建一個唯一的會話標識符(Session ID),并將與該會話相關的數據存儲在服務器端(如內存、數據庫或文件系統中)。
服務器將 Session ID 發送給客戶端(通常是通過設置一個 Cookie)。客戶端(瀏覽器)會在后續的請求中自動將這個 Cookie(包含 Session ID)發送回服務器。
服務器通過客戶端發送的 Session ID,從服務器端的存儲中找到對應的會話數據,從而恢復用戶的會話狀態。
五、cookie、session區別
由于HTTP是一種無狀態的協議,服務器單從網絡連接上無從知道客戶身份。用戶A購買了一件商品放入購物車內,當再次購買商品時服務器已經無法判斷該購買行為是屬于用戶A的會話還是用戶B的會話了。怎么辦呢?就給客戶端們頒發一個通行證吧,每人一個,無論誰訪問都必須攜帶自己通行證。這樣服務器就能從通行證上確認客戶身份了。這就是Cookie 的工作原理。
Cookie是存儲在客戶端(用戶瀏覽器)的小塊數據,可以用來記住用戶的相關信息,例如登錄憑證或偏好設置。它們隨每個HTTP請求發送給服務器,并且可以被服務器讀取以維持會話或個性化用戶體驗。
再比如想象用戶登錄銀行網站。服務器創建一個包含會話標識符的Cookie,并通過Set-Cookie頭部發送回用戶的瀏覽器。瀏覽器存儲此Cookie,并在隨后的請求中將其發送回服務器,允許服務器識別用戶并在多個頁面加載中保持他們的登錄狀態。
若不設置過期時間,則表示這個cookie的生命期為瀏覽器會話期間,關閉瀏覽器窗口,cookie就消失。這種生命期為瀏覽器會話期的cookie被稱為會話cookie。會話cookie一般不存儲在硬盤上而是保存在內存里,當然這種行為并不是規范規定的。
若設置了過期時間,瀏覽器就會把cookie保存到硬盤上,關閉后再次打開瀏覽器,這些cookie仍然有效直到超過設定的過期時間。存儲在硬盤上的cookie可以在瀏覽器的不同進程間共享。這種稱為持久Cookie。
客戶端瀏覽器訪問服務器的時候,服務器把客戶端信息以某種形式記錄在服務器上,這就是Session。客戶端瀏覽器再次訪問時只需要從該Session中查找該客戶的狀態就可以了。每個用戶訪問服務器都會建立一個session并自動分配一個SessionId,用于標識用戶的唯一身份。
也就是會話用于跟蹤用戶在多個頁面請求期間的狀態。它們通常存儲在服務器端,并且與唯一的會話標識符(通常是會話ID)相關聯,會話ID作為Cookie發送給客戶端。會話允許服務器在用戶訪問期間記住有關用戶的信息。
用戶在電子商務網站上購物。服務器為用戶創建一個會話,存儲他們的購物車項目和其他相關信息。會話ID作為Cookie發送給用戶的瀏覽器。隨著用戶在網站上導航,Cookie中的會話ID允許服務器訪問用戶會話數據,使用戶能夠無縫購物體驗。
服務器通過SessionId作為key,讀寫對應的value,這就達到了保持會話信息的目的。
所以,為什么有了cookie還要有session?
- 存儲位置和安全性
cookie 數據存儲在客戶端(用戶的瀏覽器中)。由于 Cookie 數據存儲在客戶端,用戶可以輕松查看和修改 Cookie 內容。即使通過加密和簽名增強安全性,也無法完全防止用戶篡改。Cookie 的大小有限制,通常不超過 4KB,且瀏覽器對每個域名的 Cookie 數量也有限制。
Session 數據存儲在服務器端,客戶端只保存一個 Session ID(通常通過 Cookie 傳遞)。由于數據存儲在服務器端,用戶無法直接訪問或修改 Session 數據,安全性更高。服務器端的存儲容量通常比客戶端大得多,可以存儲更復雜的數據結構。
- 數據隱私和敏感信息
Cookie 數據存儲在客戶端,即使是加密的,也存在被竊取或篡改的風險。因此,不適合存儲敏感信息(如密碼、用戶身份信息等)。Session 數據存儲在服務器端,可以安全地存儲敏感信息。例如,用戶登錄后,服務器可以將用戶的認證狀態和權限信息存儲在 Session 中,而無需暴露給客戶端。
- 分布式和可擴展性
Cookie 數據存儲在客戶端,不依賴于服務器的存儲。但如果需要在多個服務器之間共享會話數據(如分布式應用),Cookie 無法直接實現。
Session 數據存儲在服務器端,可以通過多種方式實現分布式存儲(如 Redis、數據庫等),從而支持分布式應用和負載均衡。在分布式環境中,Session ID 可以通過 Cookie 傳遞給客戶端,而實際的會話數據存儲在共享的存儲系統中,從而實現會話的共享和同步。
Session 數據存儲在服務器端,客戶端只需要發送一個輕量級的 Session ID(通常是一個短字符串),從而減輕客戶端的負擔,提高性能。
六、會話攻擊
如果客戶端存儲的 SessionID 被劫持,攻擊者確實可能通過獲取 SessionID 冒充用戶身份,從而對服務器進行非法操作。然而,雖然無法完全杜絕 SessionID 被竊取的風險,但可以通過多種防御措施來降低這種風險,并防止攻擊者利用竊取的 SessionID 進行有效攻擊。
- 使用 HTTPS 協議
通過 HTTPS 加密客戶端與服務器之間的通信,可以防止 SessionID 在傳輸過程中被中間人攻擊竊取。
- 設置 Cookie 的 HttpOnly 和 Secure 屬性
HttpOnly:設置 HttpOnly 屬性后,Cookie 無法被客戶端腳本(如 JavaScript)訪問,從而防止通過 XSS 攻擊竊取 SessionID。
Secure:設置 Secure 屬性后,Cookie 只會在 HTTPS 協議下傳輸,進一步防止 SessionID 被竊取。
- 定期更新 SessionID
在用戶登錄、權限變更或定期時間間隔內更新 SessionID,可以減少攻擊者利用舊 SessionID 的機會。同時通過檢測客戶端的 User-Agent、IP 地址或其他標識信息是否發生變化,可以判斷是否為合法用戶。如果檢測到異常,可以要求用戶重新登錄。