node.js中express-session配置項詳解
官方地址:閱讀
作用:用指定的參數(shù)創(chuàng)建一個session中間件,sesison數(shù)據(jù)不是保存在cookie中,僅僅sessionID保存到cookie中,session的數(shù)據(jù)僅僅保存在服務(wù)器端
警告:默認的服務(wù)器端的session存儲,MemoryStore不是為了生產(chǎn)環(huán)境創(chuàng)建的,大多數(shù)情況下會內(nèi)存泄露,主要用于測試和開發(fā)環(huán)境
接受的參數(shù):
cookie:也就是session ID的cookie,默認是{ path: '/', httpOnly: true, secure: false, maxAge: null }.
var Cookie = module.exports = function Cookie(options) { this.path = '/'; this.maxAge = null; this.httpOnly = true; if (options) merge(this, options); this.originalMaxAge = undefined == this.originalMaxAge ? this.maxAge : this.originalMaxAge; //默認的originalMaxAge就是this.maxAge也就是null,如果指定了originalMaxAge那么就是用戶指定的值 };
genid:產(chǎn)生一個新的sessionID的函數(shù),一個返回值是string類型的函數(shù)會被作為sessionID.這個函數(shù)第一個參數(shù)是req,所以如果你想要req中的參數(shù)產(chǎn)生sessionID還是很不錯的
默認函數(shù)是使用uid-safe這個庫產(chǎn)生id值(產(chǎn)生一個算法上安全的UID,可以用于cookie也可以用于URL。和rand-token和uid2相比,后者由于使用了%導致UID產(chǎn)生偏態(tài),同時可能對UID產(chǎn)生不必要的截斷。我們的uid-safe使用的是base64算法,其函數(shù)uid(byteLength, callback)中第一個參數(shù)是比特長度而不是字符串長度)
app.use(session({ genid: function(req) { return genuuid() // use UUIDs for session IDs }, secret: 'keyboard cat' })
源碼片段:
function generateSessionId(sess) { return uid(24); } var generateId = options.genid || generateSessionId; //如果用戶沒有傳入genid參數(shù)那么就是默認使用generateSessionId函數(shù)來完成
name:在response中sessionID這個cookie的名稱。也可以通過這個name讀取,默認是connect.sid。如果一臺機器上有多個app運行在同樣的hostname+port, 那么你需要對這個sessin的cookie進行切割,所以最好的方法還是通過name設(shè)置不同的值
name = options.name || options.key || 'connect.sid' //很顯然cookie的name默認是connect.sid,而且首先獲取到的name而不是key r cookieId = req.sessionID = getcookie(req, name, secrets);
resave:強制session保存到session store中。即使在請求中這個session沒有被修改。但是這個并不一定是必須的,如果客戶端有兩個并行的請求到你的客戶端,一個請求對session的修改可能被另外一個請求覆蓋掉,即使第二個請求并沒有修改sesion。默認是true,但是默認值已經(jīng)過時,因此以后default可能會被修改。因此好好研究你的需求選擇一個最適用的。大多數(shù)情況下你可能需要false 最好的知道你的store是否需要設(shè)置resave的方法是通過查看你的store是否實現(xiàn)了touch方法(刪除那些空閑的session。同時這個方法也會通知session store指定的session是活動態(tài)的),如果實現(xiàn)了那么你可以用resave:false,如果沒有實現(xiàn)touch方法,同時你的store對保存的session設(shè)置了一個過期的時間,那么建議你用resave:true
var resaveSession = options.resave; if (resaveSession === undefined) { deprecate('undefined resave option; provide resave option'); resaveSession = true;//如果用戶沒有指定resavedSession那么默認就是true }
我們再來看看其他的邏輯
store.get(req.sessionID, function(err, sess){ // error handling //如果報錯那么也會創(chuàng)建一個session if (err) { debug('error %j', err); if (err.code !== 'ENOENT') { next(err); return; } generate(); // no session那么就會創(chuàng)建一個session } else if (!sess) { debug('no session found'); generate(); // populate req.session //如果找到了這個session處理的代碼邏輯 } else { debug('session found'); store.createSession(req, sess); originalId = req.sessionID; originalHash = hash(sess); //originalHash保存的是找到的這個session的hash結(jié)果,如果明確指定了resave為false那么savedHash就是原來的session的結(jié)果 if (!resaveSession) { savedHash = originalHash } wrapmethods(req.session); } next(); }); }; };
其中經(jīng)過了前面的if語句后我們的savedHash就是originalHash,我們看看這個邏輯在判斷這個session是否已經(jīng)保存的時候再次用到了
function isSaved(sess) { return originalId === sess.id && savedHash === hash(sess); }
rolling:強制在每一個response中都發(fā)送session標識符的cookie。如果把expiration設(shè)置為一個過去的時間那么 那么過期時間設(shè)置為默認的值。roling默認是false。如果把這個值設(shè)置為true但是saveUnitialized設(shè)置為false,那么cookie不會被包含在響應(yīng)中(沒有初始化的session)
rollingSessions = options.rolling || false;//默認為false
我們看看rolling用于了什么環(huán)境了:
//這個方法用戶判斷是否需要在請求頭中設(shè)置cookie // determine if cookie should be set on response function shouldSetCookie(req) { // cannot set cookie without a session ID //如果沒有sessionID直接返回,這時候不用設(shè)置cookie if (typeof req.sessionID !== 'string') { return false; } //var cookieId = req.sessionID = getcookie(req, name, secrets); return cookieId != req.sessionID ? saveUninitializedSession || isModified(req.session) //rollingSessions = options.rolling || false,其中rolling表示sessionCookie在每一個響應(yīng)中都應(yīng)該被發(fā)送。也就是說如果用戶設(shè)置了rolling即使sessionID沒有被修改 //也依然會把session的cookie發(fā)送到瀏覽器 : rollingSessions || req.session.cookie.expires != null && isModified(req.session); }
很顯然,如果客戶端發(fā)送的sessionID和服務(wù)器的sessionID一致,如果你指定了rolling為true,那么還是會發(fā)送這個session的cookie到客戶端,但是如果你設(shè)置了rolling為false,那么這時候如果同時設(shè)置了req.session.cookie.expires,而且這個req.session被修改了這時候還是會把session的cookie發(fā)送到客戶端!
saveUninitialized:強制沒有“初始化”的session保存到storage中,沒有初始化的session指的是:剛被創(chuàng)建沒有被修改,如果是要實現(xiàn)登陸的session那么最好設(shè)置為false(reducing server storage usage, or complying with laws that require permission before setting a cookie) 而且設(shè)置為false還有一個好處,當客戶端沒有session的情況下并行發(fā)送多個請求時。默認是true,但是不建議使用默認值。
var saveUninitializedSession = options.saveUninitialized; /如果用戶不指定saveUninitializedSession那么提示用戶并設(shè)置saveUninitializedSession為true if (saveUninitializedSession === undefined) { deprecate('undefined saveUninitialized option; provide saveUninitialized option'); saveUninitializedSession = true; }
我們來看看這個參數(shù)用于做什么判斷,首先看看shouldSave方法
// determine if session should be saved to store //判斷是否需要把session保存到到store中 function shouldSave(req) { // cannot set cookie without a session ID if (typeof req.sessionID !== 'string') { debug('session ignored because of bogus req.sessionID %o', req.sessionID); return false; } // var saveUninitializedSession = options.saveUninitialized; // var cookieId = req.sessionID = getcookie(req, name, secrets); return !saveUninitializedSession && cookieId !== req.sessionID ? isModified(req.session) : !isSaved(req.session) }
如果用戶指明了不能保存未初始化的session,同時服務(wù)器的req.sessionID和瀏覽器發(fā)送過來的不一致,這時候只有在服務(wù)器的session修改的時候會保存。如果前面的前提不滿足那么就需要看是否已經(jīng)保存過了,如果沒有保存過那么才會保存!
這個參數(shù)還被用于決定是否需要把session的cookie發(fā)送到客戶端:
//這個方法用戶判斷是否需要在請求頭中設(shè)置cookie // determine if cookie should be set on response function shouldSetCookie(req) { // cannot set cookie without a session ID //如果沒有sessionID直接返回,這時候不用設(shè)置cookie if (typeof req.sessionID !== 'string') { return false; } //var cookieId = req.sessionID = getcookie(req, name, secrets); return cookieId != req.sessionID ? saveUninitializedSession || isModified(req.session) //rollingSessions = options.rolling || false,其中rolling表示sessionCookie在每一個響應(yīng)中都應(yīng)該被發(fā)送。也就是說如果用戶設(shè)置了rolling即使sessionID沒有被修改 //也依然會把session的cookie發(fā)送到瀏覽器 : rollingSessions || req.session.cookie.expires != null && isModified(req.session); }
如果客戶端和服務(wù)器端的sessionID不一致的前提下,如果用戶指定了保存未初始化的session那么就需要發(fā)送,否則就只有在修改的時候才發(fā)送
secret:用于對sessionID的cookie進行簽名,可以是一個string(一個secret)或者數(shù)組(多個secret)。如果指定了一個數(shù)組那么只會用 第一個元素對sessionID的cookie進行簽名,其他的用于驗證請求中的簽名。
var secret = options.secret; //unsetDestroy表示用戶是否指定了unset參數(shù)是destroy,是布爾值 if (Array.isArray(secret) && secret.length === 0) { throw new TypeError('secret option array must contain one or more strings'); } //保證secret保存的是一個數(shù)組,即使用戶傳入的僅僅是一個string if (secret && !Array.isArray(secret)) { secret = [secret]; } //必須提供secret參數(shù) if (!secret) { deprecate('req.secret; provide secret option'); }
我們看看這個secret參數(shù)用于什么情景:
//作用:用于從請求對象request中獲取session ID值,其中name就是我們在options中指定的,首先從req.headers.cookie獲取,接著從req.signedCookies中獲取,最后從req.cookies獲取 function getcookie(req, name, secrets) { var header = req.headers.cookie; var raw; var val; // read from cookie header if (header) { var cookies = cookie.parse(header); raw = cookies[name]; if (raw) { if (raw.substr(0, 2) === 's:') { //切割掉前面的字符"s:"! val = unsigncookie(raw.slice(2), secrets); //val表示false意味著客戶端傳遞過來的cookie被篡改了! if (val === false) { debug('cookie signature invalid'); val = undefined; } } else { debug('cookie unsigned') } } } // back-compat read from cookieParser() signedCookies data if (!val && req.signedCookies) { val = req.signedCookies[name]; if (val) { deprecate('cookie should be available in req.headers.cookie'); } } // back-compat read from cookieParser() cookies data if (!val && req.cookies) { raw = req.cookies[name]; if (raw) { if (raw.substr(0, 2) === 's:') { val = unsigncookie(raw.slice(2), secrets); if (val) { deprecate('cookie should be available in req.headers.cookie'); } if (val === false) { debug('cookie signature invalid'); val = undefined; } } else { debug('cookie unsigned') } } } return val; }
getcookie方法用于從請求中獲取sessionID進行解密,作為秘鑰。
// setcookie(res, name, req.sessionID, secrets[0], cookie.data); //方法作用:為HTTP響應(yīng)設(shè)置cookie,設(shè)置的cookie是把req.sessionID進行加密過后的cookie,其中name用于保存到客戶端的sessionID的cookie的名稱 function setcookie(res, name, val, secret, options) { var signed = 's:' + signature.sign(val, secret); //對要發(fā)送的cookie進行加密,密鑰為secret var data = cookie.serialize(name, signed, options); //其中options中可能有decode函數(shù),返回序列化的cookie debug('set-cookie %s', data); var prev = res.getHeader('set-cookie') || []; //獲取set-cookie頭,默認是一個空數(shù)組 var header = Array.isArray(prev) ? prev.concat(data) : Array.isArray(data) ? [prev].concat(data) : [prev, data]; //通過set-cookie,發(fā)送到客戶端 res.setHeader('set-cookie', header) }
用于setcookie方法,該方法用于對sessionID用指定的秘鑰進行簽名。
store:保存session的地方,默認是一個MemoryStore實例
store = options.store || new MemoryStore // notify user that this store is not // meant for a production environment //如果在生產(chǎn)環(huán)境下,同時store也就是用戶傳入的store(默認為MemoryStore)是MemoryStore那么給出警告 if ('production' == env && store instanceof MemoryStore) { console.warn(warning); } // generates the new session //為用于指定的store添加一個方法generate,同時為這個方法傳入req對象,在這個generate方法中為req指定了sessionID,session,session.cookie //如果用戶傳入的secure為auto, store.generate = function(req){ req.sessionID = generateId(req); req.session = new Session(req); req.session.cookie = new Cookie(cookieOptions); //用戶指定的secure參數(shù)如果是auto,那么修改req.session.cookie的secure參數(shù),并通過issecure來判斷 if (cookieOptions.secure === 'auto') { req.session.cookie.secure = issecure(req, trustProxy); } }; //查看store是否實現(xiàn)了touch方法 var storeImplementsTouch = typeof store.touch === 'function'; //為store注冊disconnect事件,在該事件中吧storeReady設(shè)置為false store.on('disconnect', function(){ storeReady = false; }); //為stroe注冊connect事件,把storeReady設(shè)置為true store.on('connect', function(){ storeReady = true; }); // expose store req.sessionStore = store;
我們知道這個store是用于保存session的地方,默認是一個MemoryStore,但是在生產(chǎn)環(huán)境下不建議使用MemoryStore,同時store有很多自定義的方法,如這里就為他添加了generate,connect,disconnect,當然也包含destroy方法。如果你對store感興趣,可以看看下面這個通用的store具有的所有的方法:
'use strict'; var EventEmitter = require('events').EventEmitter , Session = require('./session') , Cookie = require('./cookie') var Store = module.exports = function Store(options){}; //這個Store實例是一個EventEmitter實例,也就是說Store實例最后還是一個EventEmitter實例對象 Store.prototype.__proto__ = EventEmitter.prototype; //每一個store有一個默認的regenerate方法用于產(chǎn)生session Store.prototype.regenerate = function(req, fn){ var self = this; //regenerate底層調(diào)用的是destroy方法,第一個參數(shù)是req.sessionID,至于回調(diào)中的self.generate必須是對容器進行指定的 this.destroy(req.sessionID, function(err){ self.generate(req); fn(err);//最后回調(diào)fn }); //調(diào)用這個store的destory方法,銷毀req.sessionID,銷毀成功后通過剛才的store的generate方法產(chǎn)生一個sessionID }; //通過指定的sid加載一個Session實例,然后觸發(fā)函數(shù)fn(err,sess) Store.prototype.load = function(sid, fn){ var self = this; //最后調(diào)用的是Store的get方法 this.get(sid, function(err, sess){ if (err) return fn(err); if (!sess) return fn(); //如果sess為空那么調(diào)用fn()方法 var req = { sessionID: sid, sessionStore: self }; //調(diào)用createSession來完成的 sess = self.createSession(req, sess); fn(null, sess); }); }; //從一個JSON格式的sess中創(chuàng)建一個session實例,如sess={cookie:{expires:xx,originalMaxAge:xxx}} Store.prototype.createSession = function(req, sess){ var expires = sess.cookie.expires , orig = sess.cookie.originalMaxAge; //創(chuàng)建session時候獲取其中的cookie域下面的expires,originalMaxAge參數(shù) sess.cookie = new Cookie(sess.cookie); //更新session.cookie為一個Cookie實例而不再是一個{}對象了 if ('string' == typeof expires) sess.cookie.expires = new Date(expires); sess.cookie.originalMaxAge = orig; //為新構(gòu)建的cookie添加originalMaxAge屬性 req.session = new Session(req, sess); //創(chuàng)建一個session實例,其中傳入的第一個參數(shù)是req,第二個參數(shù)是sess也就是我們剛才創(chuàng)建的那個Cookie實例,簽名為sess={cookie:cookie對象} return req.session; };
unset:對沒有設(shè)置的req.session進行控制,通過delete或者設(shè)置為null。默認是keep,destory表示當回應(yīng)結(jié)束后會銷毀session,keep表示session會被保存。但是在請求中對session的修改會被忽略,也不會保存
//如果用戶指定了unset,但是unset不是destroy/keep,那么保存 if (options.unset && options.unset !== 'destroy' && options.unset !== 'keep') { throw new TypeError('unset option must be "destroy" or "keep"'); } // TODO: switch to "destroy" on next major var unsetDestroy = options.unset === 'destroy'; // determine if session should be destroyed //sessionID還存在,但是req.session已經(jīng)被銷毀了 function shouldDestroy(req) { // var unsetDestroy = options.unset === 'destroy'; return req.sessionID && unsetDestroy && req.session == null; }
我們可以看到unset只能是默認的destroy或者keep,其用于判斷是否應(yīng)該銷毀session,如果指定了unset方法為destrory,那么就會銷毀session,也就是把req.session設(shè)置為null
在版本1.5.0后,cookie-parser這個中間件已經(jīng)不是express-session工作必須的了。這個模塊可以直接對req/res中的cookie進行讀寫,使用cookie-parser可能導致一些問題,特別是當secret在兩個模塊之間存在不一致的時候。
請把secure設(shè)置為true,這是明智的。但是這需要網(wǎng)站的支持,因為secure需要HTTPS的協(xié)議。如果設(shè)置了secure,但是你使用HTTP訪問,那么cookie不會被設(shè)置,如果Node.js運行在代理上,同時使用了secure:true那么在express中需要設(shè)置”信任代理“。
var app = express() app.set('trust proxy', 1) // trust first proxy app.use(session({ secret: 'keyboard cat', resave: false, saveUninitialized: true, cookie: { secure: true } }))
如果在生產(chǎn)環(huán)境下需要使用安全的cookit,同時在測試環(huán)境也要能夠使用。那么可以使用express中的NODE_ENV參數(shù)
var app = express() var sess = { secret: 'keyboard cat', cookie: {} } if (app.get('env') === 'production') { app.set('trust proxy', 1) // trust first proxy sess.cookie.secure = true // serve secure cookies } app.use(session(sess))
cookie的secure屬性可以設(shè)置為auto,那么會按照請求的方式來判斷,如果是安全的就是secure。但是如果網(wǎng)站同時支持HTTP和HTTPS,這時候通過HTTPS設(shè)置的cookie
對于HTTP是不可見的。這在express的”trust proxy“(簡化開發(fā)和生產(chǎn)環(huán)境)正確設(shè)置的情況下特別有用。默認下:cookie.maxAge為null
這意味著,瀏覽器關(guān)閉了這個cookie也就過期了。
req.session:
// Use the session middleware app.use(session({ secret: 'keyboard cat', cookie: { maxAge: 60000 }})) // Access the session as req.session app.get('/', function(req, res, next) { var sess = req.session//用這個屬性獲取session中保存的數(shù)據(jù),而且返回的JSON數(shù)據(jù) if (sess.views) { sess.views++ res.setHeader('Content-Type', 'text/html') res.write('<p>views: ' + sess.views + '</p>') res.write('<p>expires in: ' + (sess.cookie.maxAge / 1000) + 's</p>') res.end() } else { sess.views = 1 res.end('welcome to the session demo. refresh!') } })
其中req.session是一個session對象,格式如下:
session: //req.session域下面保存的是一個Session實例,其中有cookie表示是一個對象 Session { //這里是req.session.cookie是一個Cookie實例 cookie: { path: '/', _expires: Fri May 06 2016 15:44:48 GMT+0800 (中國標準時間), originalMaxAge: 2591999960, httpOnly: true }, flash: { error: [Object] } }
Session.regenerate():
產(chǎn)生一個session,調(diào)用這個方法那么一個新的SID和Session實例就會被創(chuàng)建,同時放置在req.session中。但是第一步是銷毀指定的session
Store.prototype.regenerate = function(req, fn){ var self = this; //regenerate底層調(diào)用的是destroy方法,第一個參數(shù)是req.sessionID,至于回調(diào)中的self.generate必須是對容器進行指定的 this.destroy(req.sessionID, function(err){ self.generate(req); fn(err);//最后回調(diào)fn }); //調(diào)用這個store的destory方法,銷毀req.sessionID,銷毀成功后通過剛才的store的generate方法產(chǎn)生一個sessionID };
這時通用store提供的regenerate方法,但是generate方法一般要特定的庫進行輔助:
store.generate = function(req){ req.sessionID = generateId(req); req.session = new Session(req); req.session.cookie = new Cookie(cookieOptions); //用戶指定的secure參數(shù)如果是auto,那么修改req.session.cookie的secure參數(shù),并通過issecure來判斷 if (cookieOptions.secure === 'auto') { req.session.cookie.secure = issecure(req, trustProxy); } };
這時為express-session為store指定的generate方法
session.destory():
銷毀session,同時在req.session中被移除,但是在下一次請求的時候又會被創(chuàng)建
req.session.destroy(function(err) { // cannot access session here })
session.reload():
重新裝載session中的數(shù)據(jù)
req.session.reload(function(err) { // session updated })
session.save():
把session中的數(shù)據(jù)重新保存到store中,用內(nèi)存的內(nèi)容去替換掉store中的內(nèi)容。這個方法在HTTP的響應(yīng)后自動被調(diào)用。如果session中的數(shù)據(jù)被改變了(這個行為可以通過中間件的很多的配置來改變),正因為如此這個方法一般不用顯示調(diào)用。但是在長連接的websocket中這個方法一般需要手動調(diào)用
req.session.save(function(err) { // session saved })
session.touch():
更新maxAge屬性,一般不需要手動調(diào)用,因為session的中間件已經(jīng)替你調(diào)用了。我們看看Session是如何實現(xiàn)這個方法
function Session(req, data) { Object.defineProperty(this, 'req', { value: req }); Object.defineProperty(this, 'id', { value: req.sessionID }); if (typeof data === 'object' && data !== null) { // merge data into this, ignoring prototype properties for (var prop in data) { if (!(prop in this)) { this[prop] = data[prop] } } } } //重置".cookie.maxAge"防止在session仍然存活的時候cookie已經(jīng)過期了 defineMethod(Session.prototype, 'touch', function touch() { return this.resetMaxAge(); }); //resetMaxAge方法,用于為cookie的maxAge指定為cookie的originalMaxAge defineMethod(Session.prototype, 'resetMaxAge', function resetMaxAge() { this.cookie.maxAge = this.cookie.originalMaxAge; return this; });
也就是把session的maxAge設(shè)置為構(gòu)造Session對象的時候的初始值。
req.session.id:
唯一的,而且不會被改變。我們看看Session的構(gòu)造函數(shù)就明白了:
function Session(req, data) { Object.defineProperty(this, 'req', { value: req }); Object.defineProperty(this, 'id', { value: req.sessionID }); if (typeof data === 'object' && data !== null) { // merge data into this, ignoring prototype properties for (var prop in data) { if (!(prop in this)) { this[prop] = data[prop] } } } }
其中defineProperty方法如下:
//重寫了Object對象的defineProperty,其中defineProperty用于為這個對象指定一個函數(shù),其中第二個參數(shù)是函數(shù)的名稱,第三個是函數(shù)本身 function defineMethod(obj, name, fn) { Object.defineProperty(obj, name, { configurable: true, enumerable: false, value: fn, writable: true }); };
其中session的id值就是req.sessionID屬性而且enumerable為false,所以在控制臺是打印不出來的
req.session.cookie:
每一個session都有一個cookie對象,因此在每一次請求的時候你都可以改變session的cookie。如我們可以通過req.session.cookie.expires設(shè)置為false,這時候瀏覽器關(guān)閉cookie就不存在了
Cookie.maxAge:
req.session.cookie.maxAge返回這個cookie剩余的毫秒數(shù),當然我們也可以通過設(shè)置expires來完成
var hour = 3600000 req.session.cookie.expires = new Date(Date.now() + hour) req.session.cookie.maxAge = hour//和上面的expires等價
當maxAge設(shè)置為60000,也就是一分鐘,這時候如果已經(jīng)過去了30s,那么maxAge就會返回30000(不過要等到當前請求結(jié)束)。如果這時候我們調(diào)用req.session.touch(),那么req.session.maxAge就成了初始值了60000了
req.sessionID:
只讀的屬性。每一個session store必須是一個EventEmitter對象,同時要實現(xiàn)特定的方法。我們看看MemoryStore把:
function MemoryStore() { Store.call(this) this.sessions = Object.create(null) } //繼承了Store中的所有的原型屬性 util.inherits(MemoryStore, Store)
也就是說MemoryStore繼承了通用的Store的所有的屬性和方法,如regenerate,load,createSession,當然也實現(xiàn)了很多自己的方法如all,clear,destroy,get,length,set,touch等
下面討論的是一些其他的方法:
required方法表示:在這個store上一定會調(diào)用的方法
Recommended方法表示如果有這個方法那么在這個store上就會調(diào)用。Optional方法表示不會調(diào)用,但是為了給用戶一個統(tǒng)一的store!
store.destroy(sid, callback)
必須的方法。通過sessionID來銷毀session,如果session已經(jīng)被銷毀,那么回調(diào)函數(shù)被調(diào)用,同時傳入一個error對象
store.get(sid, callback)
必須的方法。通過sessionID從store中獲取session?;卣{(diào)函數(shù)是callback(err,session)。如果session存在那么第二個參數(shù)就是session,否則第二個參數(shù)就是null/undefined。如果error.code==="ENOENT"那么回調(diào)為callback(null,null)
store.set(sid, session, callback)
必須的方法。如果被成功設(shè)置了那么回調(diào)為callback(error)
store.touch(sid, session, callback)
推薦的方法。通過一個指定的sid和session對象去”接觸“這個session.如果接觸到了那么回調(diào)為callback(error)。session store用這個方法去刪除那些空閑的session。同時這個方法也會通知session store指定的session是活動態(tài)的。MemoryStore實現(xiàn)了這個方法:
//通過指定的sessionId獲取當前的session對象,然后把這個對象的cookie更新為新的session對應(yīng)的cookie,同時sessions中的當前session也進行更新(包括過期時間等) MemoryStore.prototype.touch = function touch(sessionId, session, callback) { var currentSession = getSession.call(this, sessionId) if (currentSession) { // update expiration currentSession.cookie = session.cookie this.sessions[sessionId] = JSON.stringify(currentSession) } callback && defer(callback) }
store.length(callback)
可選的方法。獲取store中所有的session的個數(shù),回調(diào)函數(shù)為callback(error,length)
store.clear(callback)
可選的方法,從store中吧所有的session都刪除,回調(diào)函數(shù)為callback(err)
store.all(callback)
可選的方法。以一個數(shù)組的方法獲取store中的sessions。callback(error,sessions)
session({ secret: settings.cookieSecret, //blog=s%3AisA3_M-Vso0L_gHvUnPb8Kw9DohpCCBJ.OV7p42pL91uM3jueaJATpZdlIj%2BilgxWoD8HmBSLUSo //其中secret如果是一個string,那么就是用這個string對sessionID對應(yīng)的cookie進行簽名,如果是一個數(shù)組那么只有第一個用于簽名,其他用于瀏覽器請求后的驗證 key: settings.db, //設(shè)置的cookie的名字,從上面可以看到這里指定的是blog,所以瀏覽器的請求中可以看到這里的sessionID已經(jīng)不是sessionID了,而是這里的blog name:"qinliang",//name的優(yōu)先級比key要高,如果同時設(shè)置了那么就是按照name來制定的 //沒有name時候response中為:set-cookie:blog=s%3A6OJEWycwVMmTGXcZqawrW0HNLOTJkYKm.0Slax72TMfW%2B4Tiit3Ox7NAj5S6rPWvMUr6sY02l0DE; Path=/; Expires=Thu, 28 Apr 2016 10:47:13 GMT; HttpOnly //當有name的時候resopnse中:set-cookie:qinliang=s%3ABDOjujVhV0DH9Atax_gl4DgZ4-1RGvjQ.OeUddoRalzB4iSmUHcE8oMziad4Ig7jUT1REzGcYcdg; Path=/; Expires=Thu, 28 Apr 2016 10:48:26 GMT; HttpOnly resave:true,//沒有實現(xiàn)touch方法,同時也設(shè)置了session的過期時間為30天 rolling:true,//如果設(shè)置了rolling為true,同時saveUninitialized為true,那么每一個請求都會發(fā)送沒有初始化的session! saveUninitialized:false,//設(shè)置為true,存儲空間浪費,不允許權(quán)限管理 cookie: { maxAge: 1000 * 60 * 60 * 24 * 30 }, //cookie里面全部的設(shè)置都是對于sessionID的屬性的設(shè)置,默認的屬性為{ path: '/', httpOnly: true, secure: false, maxAge: null }. //所以最后我們保存到數(shù)據(jù)庫里面的信息就是:{"cookie":{"originalMaxAge":2592000000,"expires":"2016-04-27T02:30:51.713Z","httpOnly":true,"path":"/"},"flash":{}} store: new MongoStore({ db: settings.db, host: settings.host, port: settings.port }) })
從源碼的角度來分析配置項:
(1)這里面的secret到底有什么用呢?
我們看看這個express-session到底是如何做的?
function unsigncookie(val, secrets) { for (var i = 0; i < secrets.length; i++) { var result = signature.unsign(val, secrets[i]); if (result !== false) { return result; } } return false; }
這里是通過cookie-signature進行的解密操作
// var cookieId = req.sessionID = getcookie(req, name, secrets); function getcookie(req, name, secrets) { var header = req.headers.cookie; var raw; var val; // read from cookie header if (header) { var cookies = cookie.parse(header); raw = cookies[name]; if (raw) { if (raw.substr(0, 2) === 's:') { //切割掉前面的字符"s:"! val = unsigncookie(raw.slice(2), secrets); //val表示false意味著客戶端傳遞過來的cookie被篡改了! if (val === false) { debug('cookie signature invalid'); val = undefined; } } else { debug('cookie unsigned') } } } // back-compat read from cookieParser() signedCookies data //如果從req.headers.cookie中沒有讀取到session ID的數(shù)據(jù),那么就去cookie parser的req.signedCookies中讀取 if (!val && req.signedCookies) { val = req.signedCookies[name]; if (val) { deprecate('cookie should be available in req.headers.cookie'); } } // back-compat read from cookieParser() cookies data //如果req.signedCookies中也沒有獲取到數(shù)據(jù)那么直接從req.cookies中獲取 if (!val && req.cookies) { raw = req.cookies[name]; if (raw) { if (raw.substr(0, 2) === 's:') { val = unsigncookie(raw.slice(2), secrets); if (val) { deprecate('cookie should be available in req.headers.cookie'); } if (val === false) { debug('cookie signature invalid'); val = undefined; } } else { debug('cookie unsigned') } } } return val; }
通過這里我們很容易看到對于session ID的獲取就是通過上面的secret進行簽名的,如果獲取到的sessionID已經(jīng)被修改過,那么表示這個session已經(jīng)無效了。首先是從req.headers.cookie中獲取,然后從req.signedCookies中獲取,最后從req.cookies中進行獲取!
(2)cookie字段有什么用的?
var Session = require('./session/session') , MemoryStore = require('./session/memory') , Cookie = require('./session/cookie') , Store = require('./session/store') var cookieOptions = options.cookie || {}; function generateSessionId(sess) { return uid(24); } // generates the new session store.generate = function(req){ req.sessionID = generateId(req);//產(chǎn)生一個sessionID req.session = new Session(req);//產(chǎn)生一個Session req.session.cookie = new Cookie(cookieOptions);//在req.session對象的cookie域下面保存的是一個Cookie對象 if (cookieOptions.secure === 'auto') { req.session.cookie.secure = issecure(req, trustProxy); } };
我們看看cookie字段在哪里被處理了:
var Cookie = module.exports = function Cookie(options) { this.path = '/'; this.maxAge = null; this.httpOnly = true; //最終的this就是這個新創(chuàng)建的Cookie具有這些默認的屬性,同時還具有用戶自己傳入的options參數(shù),如用戶傳入的var cookieOptions = options.cookie || {}; //也就是用戶傳入的options.cookie屬性 if (options) merge(this, options); /*這個utils.merge的源碼只有一句話: exports = module.exports = function(a, b){ if (a && b) { for (var key in b) { a[key] = b[key]; } } return a; };*/ this.originalMaxAge = undefined == this.originalMaxAge ? this.maxAge : this.originalMaxAge; //默認的originalMaxAge就是this.maxAge也就是null,如果指定了originalMaxAge那么就是用戶指定的值 };
也就是說我們在session中傳入的cookie參數(shù)也成為新創(chuàng)建的cookie的一個屬性了,而且這個這個新創(chuàng)建的cookie被保存到req.session.cookie下。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
node.js中的fs.appendFileSync方法使用說明
這篇文章主要介紹了node.js中的fs.appendFileSync方法使用說明,本文介紹了fs.appendFileSync方法說明、語法、接收參數(shù)、使用實例和實現(xiàn)源碼,需要的朋友可以參考下2014-12-12Node服務(wù)端實戰(zhàn)之操作數(shù)據(jù)庫示例詳解
這篇文章主要為大家介紹了Node服務(wù)端實戰(zhàn)之操作數(shù)據(jù)庫示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-12-12基于node+websocket+html實現(xiàn)騰訊課堂聊天室聊天功能
這篇文章主要介紹了基于node+websocket+html實現(xiàn)騰訊課堂聊天室聊天功能,本文通過截圖實例代碼給大家介紹的非常詳細,對大家的工作或?qū)W習具有一定的參考借鑒價值,需要的朋友可以參考下2020-03-03node.js中fs文件系統(tǒng)目錄操作與文件信息操作
本篇文章給大家詳細分析了node.js中fs文件系統(tǒng)目錄操作與文件信息操作的方法以及代碼詳解,需要的讀者可以參考下。2018-02-02Node解決簡單重復問題系列之Excel內(nèi)容的獲取
這篇文章主要給大家介紹了關(guān)于利用Node解決簡單重復問題系列之Excel內(nèi)容獲取的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧。2018-01-01