在Web
應用開發中Session
是在用戶和服務器之間進行交換的非持久化交互信息。當用戶登陸時,能夠在用戶和服務器之間生成Session
,而後來回交換數據,並在用戶登出時銷燬Session
。gorilla/sessions
軟件包提供了易於使用的Go
語言Session
實現。該軟件包提供了兩種不一樣的實現。第一個是文件系統存儲,它將每一個會話存儲在服務器的文件系統中。另外一個是Cookie
存儲,它使用咱們上篇文章講的SecureCookie
在客戶端上存儲會話。同時還提供了用戶自定義Session
存儲實現的選項,咱們能夠根據應用的需求本身實現Session
存儲。由於咱們的教程是學會使用爲目的就不大費周章的去實現MySQL
或者Redis
版本的Session
存儲了,咱們直接使用軟件包提供的Cookie
實現來完成本節的Session
相關內容。前端
Go Web 編程系列的每篇文章的源代碼都打了對應版本的軟件包,供你們參考。公衆號中回覆
gohttp09
獲取本文源代碼git
客戶端使用Cookie
管理用戶Session
較之在服務器進行用戶的Session
管理會有一些優點。客戶端Session
增長了應用程序的可伸縮性,由於全部的會話數據都存儲在用戶端,所以能夠將用戶的請求平衡到不一樣的遠端服務器,也沒必要在服務器端對全部用戶的會話進行統一管理,因此使用Cookie
存儲用戶Session
會更簡單一些。github
固然有優點就一定有劣勢,客戶端Cookie
的總體大小是有限制的。目前,Google Chrome
瀏覽器將Cookie
限制爲4096
個字節。shell
客戶端會話還意味着沒法終止會話,從而致使註銷不完整。若是用戶在退出前保存了Cookie
中的會話信息,則他們可使用該會話信息建立一個新的Cookie
,而後繼續使用該應用程序,爲了最大程度地下降安全風險,咱們能夠將會話Cookie
設置爲在合理的時間內過時,使用加密後的ScureCookie
存儲數據,同時還要避免在其中存儲敏感信息(即便是服務端管理Session
也不該該存儲相似密碼這種敏感信息)。數據庫
總之在考慮使用客戶端仍是服務端存儲用戶Session
時必定要根據應用的使用場景來選擇,這一點很重要。編程
在開始編碼前先來安裝一下gorilla/sessions
軟件包,瀏覽器
$ go get github.com/gorilla/sessions
複製代碼
並簡單看一下軟件包功能特性的介紹安全
Cookie
。Cookie
或服務端文件系統中的SessionStore
實現。Session
存儲提供統一的接口和基礎設施。咱們今天的示例代碼是用gorilla/sessions
提供的CookieSessionStore
實現一個簡單的系統登陸功能。bash
咱們會定義以下幾個路由:服務器
/user/login
用戶登陸驗證,驗證成功後在用戶Session
數據中標記用戶是已驗證的。/user/logout
用戶登出,會在Session
中標記用戶是未認證的。/user/secret
經過用戶Session
判斷用戶是否已認證,未認證返回403 Forbidden
錯誤。爲了達到演示目的的同時減小文章中出現過多代碼,咱們不會作前端頁面,經過命令行cURL
直接請求上面幾個URL
驗證咱們的系統登陸功能。
咱們如今項目的handler
目錄下新建一個user
子目錄,用於存放使用到用戶Session
的處理程序
...
handler/
└── user/
└── init.go
└── login.go
└── logout.go
└── secret.go
...
main.go
複製代碼
其下的四個分別是包的初始化程序init.go
以及存放上面說的三個路由處理程序的.go
源文件。
咱們把Session
存儲的初始化工做放在user
包的init
函數中,這樣首次導入user
包時便可完成相關的初始化工做。
package user
import "github.com/gorilla/sessions"
const (
//64位
cookieStoreAuthKey = "..."
//AES encrypt key必須是16或者32位
cookieStoreEncryptKey = "..."
)
var sessionStore *sessions.CookieStore
func init () {
sessionStore = sessions.NewCookieStore(
[]byte(cookieStoreAuthKey),
[]byte(cookieStoreEncryptKey),
)
sessionStore.Options = &sessions.Options{
HttpOnly: true,
MaxAge: 60 * 15,
}
}
複製代碼
// login.go
var sessionCookieName = "user-session"
func Login(w http.ResponseWriter, r *http.Request) {
session, err := sessionStore.Get(r, sessionCookieName)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// 登陸驗證
name := r.FormValue("name")
pass := r.FormValue("password")
_, err = logic.AuthenticateUser(name, pass)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
// 在session中標記用戶已經經過登陸驗證
session.Values["authenticated"] = true
err = session.Save(r, w)
fmt.Fprintln(w, "登陸成功!", err)
}
複製代碼
Cookie
中存儲用戶Session
的Cookie-Name
設置成了user-session
。MySQL
數據庫中建立users
表,並介紹了怎麼使用ORM
操做數據庫,沒有看過的同窗能夠回看一下。Session
的authenticated
中標記了用戶已經過認證。session.Values
是類型map[interface{}]interface{}
的別名,因此能夠往其中存儲任意類型的數據。登出咱們這裏就是簡單的將Session
中authenticated
的值設置成了false
.
//logout.go
func Logout(w http.ResponseWriter, r *http.Request) {
session, _ := sessionStore.Get(r, sessionCookieName)
session.Values["authenticated"] = false
session.Save(r, w)
}
複製代碼
//secret.go
func Secret(w http.ResponseWriter, r *http.Request) {
session, _ := sessionStore.Get(r, sessionCookieName)
if auth, ok := session.Values["authenticated"].(bool); !ok || !auth {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
fmt.Fprintln(w, "這裏仍是空空如也!")
}
複製代碼
Session
中存儲的數據值都是接口類型的,因此使用時要先對其進行類型斷言session.Values["authenticated"].(bool)
authenticated
的值不爲true
或者是從Session
中獲取不到對應的值,這裏直接返回HTTP 403 Forbidden
錯誤。// router.go
func RegisterRoutes(r *mux.Router) {
...
userRouter := r.PathPrefix("/user").Subrouter()
userRouter.HandleFunc("/login", user.Login).Methods("POST")
userRouter.HandleFunc("/secret", user.Secret)
userRouter.HandleFunc("/logout", user.Logout)
...
}
複製代碼
編寫完上面的Session
管理的功能後,重啓服務器,而後使用cURL
分別請求URL
驗證一下效果。
curl -XPOST -d 'name=Klein&password=123' \
-c - http://localhost:8000/user/login
複製代碼
-c
選項表示將Cookie
寫入到後面的文件中,完整格式是-c -<file_name>
,短橫線後不帶文件名錶示把Cookie
寫入到標準輸出中。
咱們能夠在下圖裏看到,Cookie
中的user-session
存儲的就是加密後的Session
數據了
若是請求中不攜帶這個Cookie
訪問/user/secret
會直接返回HTTP 403
錯誤
那麼接下來在使用cURL
請求/user/secret
時帶上上面返回的Cookie
值,看看請求是否能成功
curl --cookie "user-session=MTU4m..." http://localhost:8000/user/secret
複製代碼
Cookie
加密後的值太長了,搞得字兒好小,cURL
執行的結果顯示服務器成功地響應了咱們的請求。大家試驗的時候換成本身生成的Cookie
值請求就能夠啦。
大家實踐時也能夠用PostMan
代替cURL
試驗,不過感受PostMan
的返回不如cURL
來的明顯。
Go Web 編程系列的每篇文章的源代碼都打了對應版本的軟件包,供你們參考。公衆號中回覆
gohttp09
獲取本文源代碼