Web 開發中須要作好用戶在整個瀏覽過程的控制,由於 HTTP 協議是無狀態的,因此用戶的每一次請求都是無狀態的,服務器不知道在整個 Web 操做過程當中哪些鏈接與該用戶有關,應該如何來解決這個問題呢?Web 裏面經典的解決方案是 cookie 和 session,cookie 機制是一種客戶端機制,把用戶數據保存在客戶端,而 session 機制是一種服務器端的機制,服務器使用一種相似於散列表的結構來保存信息,每個網站訪客都會被分配給一個惟一的標誌符,即 sessionID,它的存放形式無非兩種:要麼通過 url 傳遞,要麼保存在客戶端的 cookies 裏。固然,你也能夠將 Session 保存到數據庫裏,這樣會更安全,但效率方面會有所降低。html
Cookie 是由瀏覽器維持的,存儲在客戶端的一小段文本信息,伴隨着用戶請求和頁面在 Web 服務器和瀏覽器之間傳遞。用戶每次訪問站點時,Web 應用程序均可以讀取 cookie 包含的信息。web
cookie 是有時間限制的,根據生命期不一樣分紅兩種:redis
會話 cookie
若是不設置過時時間,則表示這個 cookie 生命週期爲從建立到瀏覽器關閉爲止,只要關閉瀏覽器窗口,cookie 就消失了。這種生命期爲瀏覽會話期的 cookie 被稱爲會話 cookie。會話 cookie 通常不保存在硬盤上而是保存在內存裏。sql
持久 cookie
若是設置了過時時間,瀏覽器就會把 cookie 保存到硬盤上,關閉後再次打開瀏覽器,這些 cookie 依然有效直到超過設定的過時時間。存儲在硬盤上的 cookie 能夠在不一樣的瀏覽器進程間共享,好比兩個 IE 窗口。而對於保存在內存的 cookie,不一樣的瀏覽器有不一樣的處理方式。數據庫
設置 cookie:瀏覽器
Go 語言中經過 net/http 包中的 SetCookie 來設置:安全
http.SetCookie(w ResponseWriter, cookie *Cookie)
複製代碼
w 表示須要寫入的 response,cookie 是一個 struct,以下:bash
type Cookie struct {
Name string
Value string
Path string
Domain string
Expires time.Time
RawExpires string
// MaxAge=0 means no 'Max-Age' attribute specified.
// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
// MaxAge>0 means Max-Age attribute present and given in seconds
MaxAge int
Secure bool
HttpOnly bool
Raw string
Unparsed []string // Raw text of unparsed attribute-value pairs
}
複製代碼
讀取 cookie:服務器
cookie, _ := r.Cookie("NameKey")
或
for _, cookie := range r.Cookies() {
fmt.Fprint(w, cookie.Name)
}
複製代碼
示例:
CookieBasic.gocookie
package main
import (
"fmt"
"log"
"net/http"
"time"
)
/*
讀寫 Cookie
當客戶端第一次訪問服務端時,服務端爲客戶端發一個憑證(全局惟一的字符串)
服務端將字符串發給客戶端(寫Cookie的過程)
HTTP 請求頭(發送給服務端Cookie)
HTTP 響應頭(在服務端通知客戶端保存Cookie)
*/
// 寫 cookie
func writeCookie(w http.ResponseWriter, r *http.Request) {
expiration := time.Now()
expiration = expiration.AddDate(0, 0, 3)
cookie := http.Cookie{Name:"username", Value:"geekori", Expires:expiration}
http.SetCookie(w, &cookie)
fmt.Fprintf(w,"write cookie success")
}
// 讀 cookie
func readCookie(w http.ResponseWriter, r *http.Request) {
cookie, _ := r.Cookie("username")
fmt.Fprint(w, cookie)
}
func main() {
http.HandleFunc("/writeCookie", writeCookie)
http.HandleFunc("/readCookie", readCookie)
fmt.Println("服務器已經啓動,寫 cookie 地址:http://localhost:8800/writeCookie ,讀 cookie 地址:http://localhost:8800/readCookie")
// 啓動 HTTP 服務,並監聽端口號,開始監聽,處理請求,返回響應
err := http.ListenAndServe(":8800", nil)
if err != nil {
log.Fatal("ListenAndServe",err)
}
}
複製代碼
執行以上程序後,服務器控制檯輸出:
服務器已經啓動,寫 cookie 地址:http://localhost:8800/writeCookie ,讀 cookie 地址:http://localhost:8800/readCookie
複製代碼
在瀏覽器先訪問寫 cookie 頁面,頁面顯示:write cookie success,經過瀏覽器能夠查看該 cookie,而後訪問讀 cookie 頁面,頁面顯示:username=geekori。
MultiCookie.go
package main
import (
"fmt"
"log"
"net/http"
"strconv"
"time"
)
// 寫 cookies
func writeCookies(w http.ResponseWriter, r *http.Request) {
var cookies map[string]interface{}
cookies = make(map[string]interface{})
cookies["name"] = "Bill"
cookies["age"] = 18
cookies["salary"] = 2000
cookies["country"] = "China"
expiration := time.Now()
expiration = expiration.AddDate(0, 0, 3)
for key,value := range cookies {
v,yes := value.(string)
// 轉化失敗
if !yes {
var intV = value.(int)
v = strconv.Itoa(intV)
}
cookie := http.Cookie{Name:key, Value:v, Expires:expiration}
http.SetCookie(w, &cookie)
}
fmt.Fprintf(w,"write cookies success")
}
// 讀 cookies
func readCookies(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "<html>")
for _, cookie := range r.Cookies() {
fmt.Fprint(w, cookie.Name)
fmt.Fprint(w, "=")
fmt.Fprint(w, cookie.Value)
fmt.Fprint(w, "<br />")
}
fmt.Fprint(w, "</html>")
}
func main() {
http.HandleFunc("/writeCookies", writeCookies)
http.HandleFunc("/readCookies", readCookies)
fmt.Println("服務器已經啓動,寫 cookie 地址:http://localhost:8800/writeCookies ,讀 cookie 地址:http://localhost:8800/readCookies ")
// 啓動 HTTP 服務,並監聽端口號,開始監聽,處理請求,返回響應
err := http.ListenAndServe(":8800", nil)
if err != nil {
log.Fatal("ListenAndServe",err)
}
}
複製代碼
執行以上程序後,服務器控制檯輸出:
服務器已經啓動,寫 cookie 地址:http://localhost:8800/writeCookies ,讀 cookie 地址:http://localhost:8800/readCookies
複製代碼
在瀏覽器先訪問寫 cookie 頁面,頁面顯示:write cookies success,經過瀏覽器能夠查看該 cookie,而後訪問讀 cookie 頁面,頁面顯示:age=18 salary=2000 country=China name=Bill 。
CNCookie.go
package main
import (
"encoding/base64"
"fmt"
"log"
"net/http"
"time"
)
// 寫 cookies
func writeCNCookies(w http.ResponseWriter, r *http.Request) {
var cookies map[string]string
cookies = make(map[string]string)
cookies["name"] = "張三"
cookies["country"] = "中國"
expiration := time.Now()
expiration = expiration.AddDate(0, 0, 3)
for key,value := range cookies {
bvalue := []byte(value)
encodeString := base64.StdEncoding.EncodeToString(bvalue)
cookie := http.Cookie{Name:key, Value:encodeString, Expires:expiration}
http.SetCookie(w, &cookie)
}
fmt.Fprintf(w,"write cookies success")
}
// 讀 cookies
func readCNCookies(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "<html>")
for _, cookie := range r.Cookies() {
if cookie.Name != "name" && cookie.Name != "country" {
continue
}
fmt.Fprint(w, cookie.Name)
fmt.Fprint(w, "=")
decodeBytes,_ := base64.StdEncoding.DecodeString(cookie.Value)
value := string(decodeBytes)
fmt.Fprint(w, value)
fmt.Fprint(w, "<br />")
}
fmt.Fprint(w, "</html>")
}
func main() {
http.HandleFunc("/writeCNCookies", writeCNCookies)
http.HandleFunc("/readCNCookies", readCNCookies)
fmt.Println("服務器已經啓動,寫 cookie 地址:http://localhost:8800/writeCNCookies ,讀 cookie 地址:http://localhost:8800/readCNCookies ")
// 啓動 HTTP 服務,並監聽端口號,開始監聽,處理請求,返回響應
err := http.ListenAndServe(":8800", nil)
if err != nil {
log.Fatal("ListenAndServe",err)
}
}
複製代碼
執行以上程序後,服務器控制檯輸出:
服務器已經啓動,寫 cookie 地址:http://localhost:8800/writeCNCookies ,讀 cookie 地址:http://localhost:8800/readCNCookies
複製代碼
在瀏覽器先訪問寫 cookie 頁面,頁面顯示:write cookies success,經過瀏覽器能夠查看該 cookie,而後訪問讀 cookie 頁面,頁面顯示:country=中國 name=張三 。
Seesion 是服務器端開闢的一塊內存空間,存放着客戶端瀏覽器窗口的編號,用來記錄用戶的狀態,存放方式依然是名值對。
目前 Go 標準包沒有爲 session 提供任何支持,須要本身動手來實現 go 版本的 session 管理和建立。
session 的基本原理是由服務器爲每一個會話維護一份信息數據,客戶端和服務端依靠一個全局惟一的標識來訪問這份數據,以達到交互的目的。當用戶訪問 Web 應用時,服務端程序會隨須要建立 session,這個過程能夠歸納爲三個步驟:
以上三個步驟中,最關鍵的是如何發送這個 session 的惟一標識這一步上。考慮到 HTTP 協議的定義,數據無非能夠放到請求行、頭域或 Body 裏,因此通常來講會有兩種經常使用的方式:cookie 和 URL 重寫。
如下是結合 session 的生命週期(lifecycle),來實現 go 語言版本的 session 管理。
關於 session 管理的整個設計思路以及相應的 go 代碼示例:
Session 管理器
定義一個全局的 session 管理器
type Manager struct {
cookieName string //private cookiename
lock sync.Mutex // protects session
provider Provider
maxlifetime int64
}
func NewManager(provideName, cookieName string, maxlifetime int64) (*Manager, error) {
provider, ok := provides[provideName]
if !ok {
return nil, fmt.Errorf("session: unknown provide %q (forgotten import?)", provideName)
}
return &Manager{provider: provider, cookieName: cookieName, maxlifetime: maxlifetime}, nil
}
複製代碼
Go 實現整個的流程應該也是這樣的,在 main 包中建立一個全局的 session 管理器
var globalSessions *session.Manager
//而後在init函數中初始化
func init() {
globalSessions, _ = NewManager("memory","gosessionid",3600)
}
複製代碼
session 是保存在服務器端的數據,它能夠以任何的方式存儲,好比存儲在內存、數據庫或者文件中。所以抽象出一個 Provider 接口,用以表徵 session 管理器底層存儲結構。
type Provider interface {
SessionInit(sid string) (Session, error)
SessionRead(sid string) (Session, error)
SessionDestroy(sid string) error
SessionGC(maxLifeTime int64)
}
複製代碼
那麼 Session 接口須要實現什麼樣的功能呢?有過 Web 開發經驗的讀者知道,對於 Session 的處理基本就 設置值、讀取值、刪除值以及獲取當前 sessionID 這四個操做,因此咱們的 Session 接口也就實現這四個操做。
type Session interface {
Set(key, value interface{}) error //set session value
Get(key interface{}) interface{} //get session value
Delete(key interface{}) error //delete session value
SessionID() string //back current sessionID
}
複製代碼
以上設計思路來源於 database/sql/driver,先定義好接口,而後具體的存儲 session 的結構實現相應的接口並註冊後,相應功能這樣就可使用了,如下是用來隨需註冊存儲 session 的結構的 Register 函數的實現。
var provides = make(map[string]Provider)
// Register makes a session provide available by the provided name.
// If Register is called twice with the same name or if driver is nil,
// it panics.
func Register(name string, provider Provider) {
if provider == nil {
panic("session: Register provide is nil")
}
if _, dup := provides[name]; dup {
panic("session: Register called twice for provide " + name)
}
provides[name] = provider
}
複製代碼
全局惟一的 Session ID
Session ID 是用來識別訪問 Web 應用的每個用戶,所以必須保證它是全局惟一的(GUID),下面代碼展現瞭如何知足這一需求:
func (manager *Manager) sessionId() string {
b := make([]byte, 32)
if _, err := io.ReadFull(rand.Reader, b); err != nil {
return ""
}
return base64.URLEncoding.EncodeToString(b)
}
複製代碼
session 建立
咱們須要爲每一個來訪用戶分配或獲取與它相關連的 Session,以便後面根據 Session 信息來驗證操做。SessionStart 這個函數就是用來檢測是否已經有某個 Session 與當前來訪用戶發生了關聯,若是沒有則建立之。
func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session Session) {
manager.lock.Lock()
defer manager.lock.Unlock()
cookie, err := r.Cookie(manager.cookieName)
if err != nil || cookie.Value == "" {
sid := manager.sessionId()
session, _ = manager.provider.SessionInit(sid)
cookie := http.Cookie{Name: manager.cookieName, Value: url.QueryEscape(sid), Path: "/", HttpOnly: true, MaxAge: int(manager.maxlifetime)}
http.SetCookie(w, &cookie)
} else {
sid, _ := url.QueryUnescape(cookie.Value)
session, _ = manager.provider.SessionRead(sid)
}
return
}
複製代碼
操做值:設置、讀取和刪除
SessionStart 函數返回的是一個知足 Session 接口的變量,那麼咱們該如何用他來對 session 數據進行操做呢? 如下是相關的操做:
func count(w http.ResponseWriter, r *http.Request) {
sess := globalSessions.SessionStart(w, r)
createtime := sess.Get("createtime")
if createtime == nil {
sess.Set("createtime", time.Now().Unix())
} else if (createtime.(int64) + 360) < (time.Now().Unix()) {
globalSessions.SessionDestroy(w, r)
sess = globalSessions.SessionStart(w, r)
}
ct := sess.Get("countnum")
if ct == nil {
ct = 1
} else {
ct = ct.(int) + 1
}
sess.Set("countnum", ct)
t, _ := template.ParseFiles("./src/session/count.html")
t.Execute(w, ct)
w.Header().Set("Content-Type", "text/html")
}
複製代碼
經過上面的例子能夠看到,Session 的操做和操做 key/value 數據庫相似:Set、Get、Delete等操做
由於 Session 有過時的概念,因此咱們定義了 GC 操做,當訪問過時時間知足 GC 的觸發條件後將會引發 GC,可是當咱們進行了任意一個 session 操做,都會對Session實體進行更新,都會觸發對最後訪問時間的修改,這樣當 GC 的時候就不會誤刪除還在使用的 Session 實體。
session 重置
在 Web 應用中有用戶退出這個操做,那麼當用戶退出應用的時候,須要對該用戶的 session 數據進行銷燬操做。
//Destroy sessionid
func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request){
cookie, err := r.Cookie(manager.cookieName)
if err != nil || cookie.Value == "" {
return
} else {
manager.lock.Lock()
defer manager.lock.Unlock()
manager.provider.SessionDestroy(cookie.Value)
expiration := time.Now()
cookie := http.Cookie{Name: manager.cookieName, Path: "/", HttpOnly: true, Expires: expiration, MaxAge: -1}
http.SetCookie(w, &cookie)
}
}
複製代碼
session 銷燬
Session 管理器如何來管理銷燬,只要咱們在 Main 啓動的時候啓動:
func init() {
go globalSessions.GC()
}
func (manager *Manager) GC() {
manager.lock.Lock()
defer manager.lock.Unlock()
manager.provider.SessionGC(manager.maxlifetime)
time.AfterFunc(time.Duration(manager.maxlifetime), func() { manager.GC() })
}
複製代碼
以上 GC 充分利用了 time 包中的定時器功能,當超時 maxLifeTime 以後調用 GC 函數,這樣就能夠保證 maxLifeTime 時間內的 session 都是可用的,相似的方案也能夠用於統計在線用戶數之類的。
示例:
SessionDemo.go
package main
import (
"fmt"
"log"
"math/rand"
"net/http"
"strconv"
"time"
)
// 產生指定長度的隨機字符串
func generateRandomStr(n int) string {
var strarr = [62]string{}
result := ""
// 填充 0 到 9
for i:=0; i < 10; i++ {
strarr[i] = strconv.Itoa(i)
}
// 填充 a 到 z
for i := 0; i < 26; i++ {
strarr[i + 10] = string(i+97)
}
// 填充 A 到 Z
for i := 0; i < 26; i++ {
strarr[36+i] = string(i + 65)
}
source := rand.NewSource(time.Now().Unix())
r := rand.New(source)
for i := 0; i < n; i++ {
index := r.Intn(len(strarr))
result = result + strarr[index]
}
return result
}
var sessions map[string]string
func writeSessionID(w http.ResponseWriter) {
sessionId := generateRandomStr(20)
sessions[sessionId] = generateRandomStr(100)
expiration := time.Now()
expiration = expiration.AddDate(0,0,40)
cookie := http.Cookie{Name:"mysession_id",Value:sessionId,Expires:expiration}
http.SetCookie(w,&cookie)
}
func mysession(w http.ResponseWriter, r *http.Request) {
cookie,err := r.Cookie("mysession_id")
// 該用戶已經提交了 sessionID
if err == nil {
// 校驗 SessionID
data,exist := sessions[cookie.Value]
// 服務端存在這個 SessionID,說明用戶不是第一次訪問服務端
if exist {
fmt.Fprint(w,"該用戶已經訪問過服務器了:" + data)
} else {
// 須要從新產生 SessionID
writeSessionID(w)
fmt.Fprint(w,"該用戶第一次訪問服務器")
}
} else {
// 須要從新生產 SessionID
writeSessionID(w)
fmt.Fprint(w,"該用戶第一次訪問服務器")
}
}
func main() {
sessions = make(map[string]string)
http.HandleFunc("/",mysession)
fmt.Println("服務器已經啓動,請在瀏覽器地址欄中輸入:http://localhost:8800")
// 啓動 HTTP 服務,並監聽端口號,開始監聽,處理請求,返回響應
err := http.ListenAndServe(":8800", nil)
if err != nil {
log.Fatal("ListenAndServe",err)
}
}
複製代碼
以上代碼實現邏輯:
執行以上程序後,服務器控制檯輸出:
服務器已經啓動,請在瀏覽器地址欄中輸入:http://localhost:8800
複製代碼
首次訪問頁面顯示:「該用戶第一次訪問服務器」,刷新頁面後顯示:該用戶已經訪問過服務器了:mR7jYg3X8R10CXGAuNLwFb97jP9tPgKGSnwOE6eg0cIrWygfUk3RvbLvnNRrk0UnkFczuXGqdIAN17VyiMwBIre9izpgymRsLCNe。
/goweb/src/session/src/session_library/MySession.go
package geekori_session
import (
"fmt"
"time"
"container/list"
"sync"
"encoding/base64"
"math/rand"
"net/http"
"net/url"
)
var pder = &Provider{list: list.New()}
func GetSession() *Provider {
return pder
}
type SessionStore struct {
sid string //session id惟一標示
timeAccessed time.Time //最後訪問時間
value map[interface{}]interface{} //session裏面存儲的值
}
type Provider struct {
lock sync.Mutex //用來鎖
sessions map[string]*list.Element //用來存儲在內存
list *list.List //用來作gc
}
func (this *SessionStore) Set(key, value interface{}) error {
this.value[key] = value
pder.SessionUpdate(this.sid)
return nil
}
func (this *SessionStore) Get(key interface{}) interface{} {
pder.SessionUpdate(this.sid)
if v, ok := this.value[key]; ok {
return v
} else {
return nil
}
}
func (this *SessionStore) Delete(key interface{}) error {
delete(this.value, key)
pder.SessionUpdate(this.sid)
return nil
}
func (st *SessionStore) SessionID() string {
return st.sid
}
func (pder *Provider) SessionInit(sid string) (*SessionStore, error) {
pder.lock.Lock()
defer pder.lock.Unlock()
v := make(map[interface{}]interface{}, 0)
newsess := &SessionStore{sid: sid, timeAccessed: time.Now(), value: v}
element := pder.list.PushBack(newsess)
pder.sessions[sid] = element
return newsess, nil
}
func (pder *Provider) SessionRead(sid string) (*SessionStore, error) {
if element, ok := pder.sessions[sid]; ok {
return element.Value.(*SessionStore), nil
} else {
sess, err := pder.SessionInit(sid)
return sess, err
}
return nil, nil
}
func (pder *Provider) SessionDestroy(sid string) error {
if element, ok := pder.sessions[sid]; ok {
delete(pder.sessions, sid)
pder.list.Remove(element)
return nil
}
return nil
}
func (pder *Provider) SessionGC(maxlifetime int64) {
pder.lock.Lock()
defer pder.lock.Unlock()
for {
element := pder.list.Back()
if element == nil {
break
}
if (element.Value.(*SessionStore).timeAccessed.Unix() + maxlifetime) < time.Now().Unix() {
pder.list.Remove(element)
delete(pder.sessions, element.Value.(*SessionStore).sid)
} else {
break
}
}
}
func (this *Provider) SessionUpdate(sid string) error {
this.lock.Lock()
defer this.lock.Unlock()
if element, ok := this.sessions[sid]; ok {
element.Value.(*SessionStore).timeAccessed = time.Now()
this.list.MoveToFront(element)
return nil
}
return nil
}
func init() {
pder.sessions = make(map[string]*list.Element, 0)
Register("memory", pder)
}
var providers = make(map[string]*Provider)
type Manager struct {
cookieName string // Cookie名稱
lock sync.Mutex // 同步鎖
provider *Provider
maxLifeTime int64
}
type Session interface {
Set(key, value interface{}) error // set session value
Get(key interface{}) interface{} // get session value
Delete(key interface{}) error // delete session value
SessionID() string // back current sessionID
}
func NewManager(provideName, cookieName string, maxLifeTime int64) (*Manager, error) {
provider, ok := providers[provideName]
if !ok {
return nil, fmt.Errorf("session: unknown provide %q (forgotten import?)", provideName)
}
return &Manager{provider: provider, cookieName: cookieName, maxLifeTime: maxLifeTime}, nil
}
func Register(name string, provider *Provider) {
if provider == nil {
panic("session: Register provider is nil")
}
if _, dup := providers[name]; dup {
panic("session: Register called twice for provider " + name)
}
providers[name] = provider
}
func (manager *Manager) sessionId() string {
b := make([]byte, 32)
if _, err := rand.Read(b); err != nil {
return ""
}
return base64.URLEncoding.EncodeToString(b)
}
func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session Session) {
manager.lock.Lock()
defer manager.lock.Unlock()
cookie, err := r.Cookie(manager.cookieName)
// 第一次訪問服務端,尚未生成Session
if err != nil || cookie.Value == "" {
// 獲取Session ID
sid := manager.sessionId()
// 初始化Session
session, _ = manager.provider.SessionInit(sid)
// 建立Cookie對象,並將session id保存到Cookie對象中
cookie := http.Cookie{Name: manager.cookieName, Value: url.QueryEscape(sid), Path: "/", HttpOnly: true, MaxAge: int(manager.maxLifeTime)}
// 向客戶端寫入包含Session ID的
http.SetCookie(w, &cookie)
} else { // Session已經找到
// 獲取Session ID
sid, _ := url.QueryUnescape(cookie.Value)
// 找到Session對象
session, _ = manager.provider.SessionRead(sid)
}
return
}
func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request){
cookie, err := r.Cookie(manager.cookieName)
if err != nil || cookie.Value == "" {
return
} else {
manager.lock.Lock()
defer manager.lock.Unlock()
manager.provider.SessionDestroy(cookie.Value)
expiration := time.Now()
cookie := http.Cookie{Name: manager.cookieName, Path: "/", HttpOnly: true, Expires: expiration, MaxAge: -1}
http.SetCookie(w, &cookie)
}
}
func (manager *Manager) GC() {
manager.lock.Lock()
defer manager.lock.Unlock()
manager.provider.SessionGC(manager.maxLifeTime)
time.AfterFunc(time.Duration(manager.maxLifeTime), func() { manager.GC() })
}
複製代碼
/goweb/src/session/TestSession.go
package main
import (
"net/http"
"fmt"
"log"
"session_library"
"time"
"html/template"
)
func count(w http.ResponseWriter, r *http.Request) {
sess := globalSessions.SessionStart(w, r)
createtime := sess.Get("createtime")
if createtime == nil {
sess.Set("createtime", time.Now().Unix())
} else if (createtime.(int64) + 360) < (time.Now().Unix()) {
globalSessions.SessionDestroy(w, r)
sess = globalSessions.SessionStart(w, r)
}
ct := sess.Get("countnum")
if ct == nil {
ct = 1
} else {
ct = ct.(int) + 1
}
sess.Set("countnum", ct)
t, _ := template.ParseFiles("./src/session/count.html")
t.Execute(w, ct)
w.Header().Set("Content-Type", "text/html")
}
var globalSessions *geekori_session.Manager
func init() {
// redis
globalSessions, _ = geekori_session.NewManager("memory", "gosessionid", 3600)
// goroutine,檢測Session是否到期
go globalSessions.GC()
}
func main() {
http.HandleFunc("/count", count) //設置訪問的路由
fmt.Println("服務器已經啓動,請在瀏覽器地址欄中輸入 http://localhost:8800/count")
err := http.ListenAndServe(":8800", nil) //設置監聽的端口
fmt.Println("監聽以後")
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
複製代碼
/goweb/src/session/count.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>計數器</title>
</head>
<body>
<h1>
計數器:{{ . }}
</h1>
</body>
</html>
複製代碼
執行以上程序後,服務器控制檯輸出:
服務器已經啓動,請在瀏覽器地址欄中輸入 http://localhost:8800/count
複製代碼
訪問 count 的頁面,顯示: 「計數器:1」,刷新頁面會作計數累加。關閉瀏覽器後從新打開也會繼續累加。
session 劫持是一種普遍存在的比較嚴重的安全威脅,在 session 技術中,客戶端和服務端經過 session 的標識符來維護會話, 但這個標識符很容易就能被嗅探到,從而被其餘人利用,它是中間人攻擊的一種類型。
session 劫持防範措施:
cookieonly 和 token
如何有效的防止 session 劫持呢?
其中一個解決方案就是 sessionID 的值只容許 cookie 設置,而不是經過 URL 重置方式設置,同時設置 cookie 的 httponly 爲 true,這個屬性能夠設置是否可經過客戶端腳本訪問這個設置的 cookie,第一這樣能夠防止這個 cookie 被 XSS 讀取從而引發 session 劫持,第二 cookie 設置不會像 URL 重置方式那麼容易獲取 sessionID。
第二步就是在每一個請求裏面加上 token,實現相似前面章節裏面講的防止 form 重複遞交相似的功能,咱們在每一個請求裏面加上一個隱藏的 token,而後每次驗證這個 token,從而保證用戶的請求都是惟一性。
h := md5.New()
salt:="astaxie%^7&8888"
io.WriteString(h,salt+time.Now().String())
token:=fmt.Sprintf("%x",h.Sum(nil))
if r.Form["token"]!=token{
//提示登陸
}
sess.Set("token",token)
複製代碼
間隔生成新的 SID
還有一個解決方案就是,給 session 額外設置一個建立時間的值,一旦過了必定的時間,就銷燬這個 sessionID,從新生成新的 session,這樣能夠必定程度上防止 session 劫持的問題。
createtime := sess.Get("createtime")
if createtime == nil {
sess.Set("createtime", time.Now().Unix())
} else if (createtime.(int64) + 60) < (time.Now().Unix()) {
globalSessions.SessionDestroy(w, r)
sess = globalSessions.SessionStart(w, r)
}
複製代碼
session 啓動後,設置了一個值,用於記錄生成 sessionID 的時間。經過判斷每次請求是否過時(這裏設置了60秒)按期生成新的 ID,這樣使得攻擊者獲取有效 sessionID 的機會大大下降。
上面兩個手段的組合能夠在實踐中消除 session 劫持的風險,一方面, 因爲 sessionID 頻繁改變,使攻擊者難有機會獲取有效的 sessionID;另外一方面,由於 sessionID 只能在 cookie 中傳遞,而後設置了 httponly,因此基於 URL 攻擊的可能性爲零,同時被 XSS 獲取 sessionID 也不可能。最後,因爲咱們還設置了 MaxAge=0,這樣就至關於 session cookie 不會留在瀏覽器的歷史記錄裏面。