// Beego (http://beego.me/) // @description beego is an open-source, high-performance web framework for the Go programming language. // @link http://github.com/astaxie/beego for the canonical source repository // @license http://github.com/astaxie/beego/blob/master/LICENSE // @authors astaxie package session import ( "crypto/hmac" "crypto/md5" "crypto/rand" "crypto/sha1" "encoding/hex" "encoding/json" "fmt" "io" "net/http" "net/url" "time" ) // SessionStore contains all data for one session process with specific id. type SessionStore 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 SessionRelease(w http.ResponseWriter) // release the resource & save data to provider & return the data Flush() error //delete all data } // Provider contains global session methods and saved SessionStores. // it can operate a SessionStore by its id. type Provider interface { SessionInit(gclifetime int64, config string) error SessionRead(sid string) (SessionStore, error) SessionExist(sid string) bool SessionRegenerate(oldsid, sid string) (SessionStore, error) SessionDestroy(sid string) error SessionAll() int //get all active session SessionGC() } 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, provide Provider) { if provide == nil { panic("session: Register provide is nil") } if _, dup := provides[name]; dup { panic("session: Register called twice for provider " + name) } provides[name] = provide } type managerConfig struct { CookieName string `json:"cookieName"` EnableSetCookie bool `json:"enableSetCookie,omitempty"` Gclifetime int64 `json:"gclifetime"` Maxlifetime int64 `json:"maxLifetime"` Secure bool `json:"secure"` SessionIDHashFunc string `json:"sessionIDHashFunc"` SessionIDHashKey string `json:"sessionIDHashKey"` CookieLifeTime int `json:"cookieLifeTime"` ProviderConfig string `json:"providerConfig"` } // Manager contains Provider and its configuration. type Manager struct { provider Provider config *managerConfig } // Create new Manager with provider name and json config string. // provider name: // 1. cookie // 2. file // 3. memory // 4. redis // 5. mysql // json config: // 1. is https default false // 2. hashfunc default sha1 // 3. hashkey default beegosessionkey // 4. maxage default is none func NewManager(provideName, config string) (*Manager, error) { provider, ok := provides[provideName] if !ok { return nil, fmt.Errorf("session: unknown provide %q (forgotten import?)", provideName) } cf := new(managerConfig) cf.EnableSetCookie = true err := json.Unmarshal([]byte(config), cf) if err != nil { return nil, err } if cf.Maxlifetime == 0 { cf.Maxlifetime = cf.Gclifetime } err = provider.SessionInit(cf.Maxlifetime, cf.ProviderConfig) if err != nil { return nil, err } if cf.SessionIDHashFunc == "" { cf.SessionIDHashFunc = "sha1" } if cf.SessionIDHashKey == "" { cf.SessionIDHashKey = string(generateRandomKey(16)) } return &Manager{ provider, cf, }, nil } // Start session. generate or read the session id from http request. // if session id exists, return SessionStore with this id. func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session SessionStore) { cookie, err := r.Cookie(manager.config.CookieName) if err != nil || cookie.Value == "" { sid := manager.sessionId(r) session, _ = manager.provider.SessionRead(sid) cookie = &http.Cookie{Name: manager.config.CookieName, Value: url.QueryEscape(sid), Path: "/", HttpOnly: true, Secure: manager.config.Secure} if manager.config.CookieLifeTime >= 0 { cookie.MaxAge = manager.config.CookieLifeTime } if manager.config.EnableSetCookie { http.SetCookie(w, cookie) } r.AddCookie(cookie) } else { sid, _ := url.QueryUnescape(cookie.Value) if manager.provider.SessionExist(sid) { session, _ = manager.provider.SessionRead(sid) } else { sid = manager.sessionId(r) session, _ = manager.provider.SessionRead(sid) cookie = &http.Cookie{Name: manager.config.CookieName, Value: url.QueryEscape(sid), Path: "/", HttpOnly: true, Secure: manager.config.Secure} if manager.config.CookieLifeTime >= 0 { cookie.MaxAge = manager.config.CookieLifeTime } if manager.config.EnableSetCookie { http.SetCookie(w, cookie) } r.AddCookie(cookie) } } return } // Destroy session by its id in http request cookie. func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request) { cookie, err := r.Cookie(manager.config.CookieName) if err != nil || cookie.Value == "" { return } else { manager.provider.SessionDestroy(cookie.Value) expiration := time.Now() cookie := http.Cookie{Name: manager.config.CookieName, Path: "/", HttpOnly: true, Expires: expiration, MaxAge: -1} http.SetCookie(w, &cookie) } } // Get SessionStore by its id. func (manager *Manager) GetSessionStore(sid string) (sessions SessionStore, err error) { sessions, err = manager.provider.SessionRead(sid) return } // Start session gc process. // it can do gc in times after gc lifetime. func (manager *Manager) GC() { manager.provider.SessionGC() time.AfterFunc(time.Duration(manager.config.Gclifetime)*time.Second, func() { manager.GC() }) } // Regenerate a session id for this SessionStore who's id is saving in http request. func (manager *Manager) SessionRegenerateId(w http.ResponseWriter, r *http.Request) (session SessionStore) { sid := manager.sessionId(r) cookie, err := r.Cookie(manager.config.CookieName) if err != nil && cookie.Value == "" { //delete old cookie session, _ = manager.provider.SessionRead(sid) cookie = &http.Cookie{Name: manager.config.CookieName, Value: url.QueryEscape(sid), Path: "/", HttpOnly: true, Secure: manager.config.Secure, } } else { oldsid, _ := url.QueryUnescape(cookie.Value) session, _ = manager.provider.SessionRegenerate(oldsid, sid) cookie.Value = url.QueryEscape(sid) cookie.HttpOnly = true cookie.Path = "/" } if manager.config.CookieLifeTime >= 0 { cookie.MaxAge = manager.config.CookieLifeTime } http.SetCookie(w, cookie) r.AddCookie(cookie) return } // Get all active sessions count number. func (manager *Manager) GetActiveSession() int { return manager.provider.SessionAll() } // Set hash function for generating session id. func (manager *Manager) SetHashFunc(hasfunc, hashkey string) { manager.config.SessionIDHashFunc = hasfunc manager.config.SessionIDHashKey = hashkey } // Set cookie with https. func (manager *Manager) SetSecure(secure bool) { manager.config.Secure = secure } // generate session id with rand string, unix nano time, remote addr by hash function. func (manager *Manager) sessionId(r *http.Request) (sid string) { bs := make([]byte, 24) if _, err := io.ReadFull(rand.Reader, bs); err != nil { return "" } sig := fmt.Sprintf("%s%d%s", r.RemoteAddr, time.Now().UnixNano(), bs) if manager.config.SessionIDHashFunc == "md5" { h := md5.New() h.Write([]byte(sig)) sid = hex.EncodeToString(h.Sum(nil)) } else if manager.config.SessionIDHashFunc == "sha1" { h := hmac.New(sha1.New, []byte(manager.config.SessionIDHashKey)) fmt.Fprintf(h, "%s", sig) sid = hex.EncodeToString(h.Sum(nil)) } else { h := hmac.New(sha1.New, []byte(manager.config.SessionIDHashKey)) fmt.Fprintf(h, "%s", sig) sid = hex.EncodeToString(h.Sum(nil)) } return }