Go Web編程--SecureCookie實現客戶端Session管理

Web應用開發中Session是在用戶和服務器之間進行交換的非持久化交互信息。當用戶登陸時,能夠在用戶和服務器之間生成Session,而後來回交換數據,並在用戶登出時銷燬Sessiongorilla/sessions軟件包提供了易於使用的Go語言Session實現。該軟件包提供了兩種不一樣的實現。第一個是文件系統存儲,它將每一個會話存儲在服務器的文件系統中。另外一個是Cookie存儲,它使用咱們上篇文章講的SecureCookie在客戶端上存儲會話。同時還提供了用戶自定義Session存儲實現的選項,咱們能夠根據應用的需求本身實現Session存儲。由於咱們的教程是學會使用爲目的就不大費周章的去實現MySQL或者Redis版本的Session存儲了,咱們直接使用軟件包提供的Cookie實現來完成本節的Session相關內容。前端

Go Web 編程系列的每篇文章的源代碼都打了對應版本的軟件包,供你們參考。公衆號中回覆gohttp09獲取本文源代碼git

使用Cookie存儲用戶Session的優缺點

客戶端使用Cookie管理用戶Session較之在服務器進行用戶的Session管理會有一些優點。客戶端Session增長了應用程序的可伸縮性,由於全部的會話數據都存儲在用戶端,所以能夠將用戶的請求平衡到不一樣的遠端服務器,也沒必要在服務器端對全部用戶的會話進行統一管理,因此使用Cookie存儲用戶Session會更簡單一些。github

固然有優點就一定有劣勢,客戶端Cookie的總體大小是有限制的。目前,Google Chrome瀏覽器將Cookie限制爲4096個字節。shell

客戶端會話還意味着沒法終止會話,從而致使註銷不完整。若是用戶在退出前保存了Cookie中的會話信息,則他們可使用該會話信息建立一個新的Cookie,而後繼續使用該應用程序,爲了最大程度地下降安全風險,咱們能夠將會話Cookie設置爲在合理的時間內過時,使用加密後的ScureCookie存儲數據,同時還要避免在其中存儲敏感信息(即便是服務端管理Session也不該該存儲相似密碼這種敏感信息)。數據庫

總之在考慮使用客戶端仍是服務端存儲用戶Session時必定要根據應用的使用場景來選擇,這一點很重要。編程

安裝gorilla/sessions

在開始編碼前先來安裝一下gorilla/sessions軟件包,瀏覽器

$ go get github.com/gorilla/sessions
複製代碼

並簡單看一下軟件包功能特性的介紹安全

  • 方便地設置簽名(也能夠選擇加密)的Cookie
  • 自帶將會話存儲在Cookie或服務端文件系統中的SessionStore實現。
  • 支持Flash消息:讀取即銷燬的會話數據。
  • 支持方便地切換會話數據的持久化方式。
  • 爲不一樣的Session存儲提供統一的接口和基礎設施。

演示用戶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存儲

咱們把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中存儲用戶SessionCookie-Name設置成了user-session
  • 登陸驗證就是簡單的用戶名和密碼查找匹配的用戶,在以前的文章應用數據庫應用 ORM兩篇文章中有在MySQL數據庫中建立users表,並介紹了怎麼使用ORM操做數據庫,沒有看過的同窗能夠回看一下。
  • 登陸驗證成功後在Sessionauthenticated中標記了用戶已經過認證。session.Values是類型map[interface{}]interface{}的別名,因此能夠往其中存儲任意類型的數據。

實現登出

登出咱們這裏就是簡單的將Sessionauthenticated的值設置成了false.

//logout.go
func Logout(w http.ResponseWriter, r *http.Request) {
   session, _ := sessionStore.Get(r, sessionCookieName)
   
   session.Values["authenticated"] = false
   session.Save(r, w)
}
複製代碼

使用Session認證用戶

//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管理功能

編寫完上面的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獲取本文源代碼

前文回顧

Go Web 編程--如何確保Cookie數據的安全傳輸

Go Web編程--應用ORM

Go Web編程--應用ORM

五分鐘用Docker快速搭建Go開發環境

深刻學習用Go編寫HTTP服務器

相關文章
相關標籤/搜索