session是在服務(wù)器端實(shí)現(xiàn)的一種用戶和服務(wù)器之間認(rèn)證的解決方案肴掷。
session創(chuàng)建過程
session的基本原理是由服務(wù)器為每個(gè)會(huì)話維護(hù)一份信息數(shù)據(jù),客戶端和服務(wù)端依靠一個(gè)全局唯一的標(biāo)識(shí)來(lái)訪問這份數(shù)據(jù)宴杀,以達(dá)到交互的目的。當(dāng)用戶訪問Web應(yīng)用時(shí)筑舅,服務(wù)端程序會(huì)隨需要?jiǎng)?chuàng)建session媒吗,這個(gè)過程可以概括為三個(gè)步驟:
1)生成全局唯一標(biāo)識(shí)符(sessionid)辈毯;
2)開辟數(shù)據(jù)存儲(chǔ)空間坝疼。可以將會(huì)話數(shù)據(jù)寫到文件里或存儲(chǔ)在數(shù)據(jù)庫(kù)中漓摩,這樣雖然會(huì)增加I/O開銷裙士,但是它可以實(shí)現(xiàn)某種程度的session持久化,也更有利于session的共享管毙;
3)將session的全局唯一標(biāo)示符發(fā)送給客戶端(最關(guān)鍵)腿椎;
如何發(fā)送session的唯一標(biāo)識(shí)(sessionID)
1)Cookie服務(wù)端通過設(shè)置Set-cookie頭就可以將session的標(biāo)識(shí)符傳送到客戶端,而客戶端此后的每一次請(qǐng)求都會(huì)帶上這個(gè)標(biāo)識(shí)符夭咬;
2)URL重寫啃炸,就是在返回給用戶的頁(yè)面里的所有的URL后面追加session標(biāo)識(shí)符,這樣用戶在收到響應(yīng)之后卓舵,無(wú)論點(diǎn)擊響應(yīng)頁(yè)面里的哪個(gè)鏈接或提交表單南用,都會(huì)自動(dòng)帶上session標(biāo)識(shí)符,從而就實(shí)現(xiàn)了會(huì)話的保持掏湾。
雖然第二種做法比較麻煩裹虫,但是如果客戶端禁用了cookie的話,此種方案將會(huì)是首選融击。
實(shí)現(xiàn)Session管理
1筑公、session管理設(shè)計(jì)
session管理涉及到如下幾個(gè)因素:
1)全局session管理器;
2)保證sessionID的全局唯一性尊浪;
3)為每個(gè)客戶關(guān)聯(lián)一個(gè)session匣屡;
4)session的存儲(chǔ)(內(nèi)存、文件拇涤、數(shù)據(jù)庫(kù)等)捣作;
5)session過期處理;
2鹅士、Session管理器
定義一個(gè)全局的session管理器:
type SessionManager struct {
????cookieName string????// private cookiename
????lock sync.Mutex ????????// protects session
????provider Provider
????maxLifeTime int64
}
func NewManager(provideName, cookieName string, maxLifeTime int64) (*SessionManager, error) {
????provider, ok := provides[provideName]
????if !ok {
????????return nil, fmt.Errorf("session: unknown provide %q(forgotten import?)", provideName)
????}
????return &SessionManager{provider: provider, cookieName: cookieName, maxLifeTime: maxLifeTime}, nil
}
在main.go中創(chuàng)建一個(gè)全局的Session管理器:
var globalSessions *session.SessionManager
func init() {
? ? globalSessions, _ = NewManager("memory", "gosid", 3600)
}
session是保存在服務(wù)器端的數(shù)據(jù)券躁,它可以以任何的方式存儲(chǔ),比如存儲(chǔ)在內(nèi)存、數(shù)據(jù)庫(kù)或者文件中嘱朽。
我們抽象出一個(gè)Provider接口旭贬,用以表征session管理器底層存儲(chǔ)結(jié)構(gòu)。
type Provider interface {
????SessionInit(sid string) (Session, error)
????SessionRead(sid string) (Session, error)
????SessionDestroy(sid string) error
????SessionGC(maxLifeTime int64)
}
其中:
SessionInit():實(shí)現(xiàn)Session的初始化搪泳,操作成功則返回此新的Session變量;
SessionRead():返回sid所代表的Session變量扼脐,如果不存在岸军,那么將以sid為參數(shù)調(diào)用SessionInit函數(shù)創(chuàng)建并返回一個(gè)新的Session變量;
SessionDestroy():用來(lái)銷毀sid對(duì)應(yīng)的Session變量瓦侮;
SessionGC():根據(jù)maxLifeTime來(lái)刪除過期的數(shù)據(jù)艰赞;
Session接口實(shí)現(xiàn)功能:
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
}
注冊(cè)Session實(shí)現(xiàn):
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 provider is nil")
? ? }
? ? if _, dup := provides[name]; dup {
? ? ? ? panic("session: Register called twice for provider " + name)
? ? }
? ? provides[name] = provider
}
全局唯一的SessionID
import (
????crand "crypto/rand"
????"encoding/base64"
????"fmt"
????"io"
)
func main() {
????sessionid := sessionId()
????fmt.Println("sessionid:", sessionid)
}
func sessionId() string {
????r := make([]byte, 32)
????if _, err := io.ReadFull(crand.Reader, r); err != nil {
????????return ""
????}
????return base64.URLEncoding.EncodeToString(r)
}
session創(chuàng)建
檢測(cè)是否已經(jīng)有某個(gè)Session與當(dāng)前來(lái)訪用戶發(fā)生了關(guān)聯(lián),如果沒有則創(chuàng)建肚吏。
func (manager *SessionManager) 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
}
Session的調(diào)用:
func login(w http.ResponseWriter, r *http.Request) {
? ? sess := globalSessions.SessionStart(w, r)
? ? r.ParseForm()
? ? if r.Method == "GET" {
? ? ? ? sid = sess.Get("uid")
? ? } else {
? ? ? ? sess.Set("uid", r.Form["username"])
? ? }
}
Session操作:設(shè)置方妖、讀取和刪除
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 {
????????sess.Set("countnum", 1)
????} else {
????????sess.Set("countnum", (ct.(int) + 1))
????}
????fmt.Println("countnum:" + countnum)
}
session重置
在Web中用戶退出登錄時(shí),需要對(duì)該用戶的session數(shù)據(jù)進(jìn)行銷毀操作罚攀。
func (manager *SessionManager) 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銷毀
在main.go啟動(dòng)的時(shí)候党觅,啟動(dòng):
func init() {
? ? go globalSessions.GC()
}
func (manager *SessionManager) GC() {
? ? manager.lock.Lock()
? ? defer manager.lock.Unlock()
? ? manager.provider.SessionGC(manager.maxLifeTime)
? ? time.AfterFunc(time.Duration(manager.maxLifeTime), func() {
? ? ? ? manager.GC()
? ? })
}
以上代碼中,GC利用了time包中的定時(shí)器功能斋泄,當(dāng)超時(shí)maxLifeTime之后調(diào)用GC函數(shù)杯瞻,這樣就可以保證maxLifeTime時(shí)間內(nèi)的session都是可用的,類似的方案也可以用于統(tǒng)計(jì)在線用戶數(shù)之類的炫掐。
學(xué)習(xí)代碼:https://github.com/astaxie/beego/tree/master/session