溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

node.js中express-session配置項(xiàng)詳解

發(fā)布時(shí)間:2020-08-31 19:33:18 來源:腳本之家 閱讀:345 作者:liangklfang 欄目:web開發(fā)

官方地址:閱讀

作用:用指定的參數(shù)創(chuàng)建一個(gè)session中間件,sesison數(shù)據(jù)不是保存在cookie中,僅僅sessionID保存到cookie中,session的數(shù)據(jù)僅僅保存在服務(wù)器

警告:默認(rèn)的服務(wù)器端的session存儲(chǔ),MemoryStore不是為了生產(chǎn)環(huán)境創(chuàng)建的,大多數(shù)情況下會(huì)內(nèi)存泄露,主要用于測(cè)試和開發(fā)環(huán)境

接受的參數(shù):

cookie:也就是session ID的cookie,默認(rèn)是{ 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; 
 //默認(rèn)的originalMaxAge就是this.maxAge也就是null,如果指定了originalMaxAge那么就是用戶指定的值 
}; 

genid:產(chǎn)生一個(gè)新的sessionID的函數(shù),一個(gè)返回值是string類型的函數(shù)會(huì)被作為sessionID.這個(gè)函數(shù)第一個(gè)參數(shù)是req,所以如果你想要req中的參數(shù)產(chǎn)生sessionID還是很不錯(cuò)的

默認(rèn)函數(shù)是使用uid-safe這個(gè)庫(kù)產(chǎn)生id值(產(chǎn)生一個(gè)算法上安全的UID,可以用于cookie也可以用于URL。和rand-token和uid2相比,后者由于使用了%導(dǎo)致UID產(chǎn)生偏態(tài),同時(shí)可能對(duì)UID產(chǎn)生不必要的截?cái)?。我們的uid-safe使用的是base64算法,其函數(shù)uid(byteLength, callback)中第一個(gè)參數(shù)是比特長(zhǎng)度而不是字符串長(zhǎng)度)

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ù)那么就是默認(rèn)使用generateSessionId函數(shù)來完成 

name:在response中sessionID這個(gè)cookie的名稱。也可以通過這個(gè)name讀取,默認(rèn)是connect.sid。如果一臺(tái)機(jī)器上有多個(gè)app運(yùn)行在同樣的hostname+port, 那么你需要對(duì)這個(gè)sessin的cookie進(jìn)行切割,所以最好的方法還是通過name設(shè)置不同的值

name = options.name || options.key || 'connect.sid'
  //很顯然cookie的name默認(rèn)是connect.sid,而且首先獲取到的name而不是key 
r cookieId = req.sessionID = getcookie(req, name, secrets); 

resave:強(qiáng)制session保存到session store中。即使在請(qǐng)求中這個(gè)session沒有被修改。但是這個(gè)并不一定是必須的,如果客戶端有兩個(gè)并行的請(qǐng)求到你的客戶端,一個(gè)請(qǐng)求對(duì)session的修改可能被另外一個(gè)請(qǐng)求覆蓋掉,即使第二個(gè)請(qǐng)求并沒有修改sesion。默認(rèn)是true,但是默認(rèn)值已經(jīng)過時(shí),因此以后default可能會(huì)被修改。因此好好研究你的需求選擇一個(gè)最適用的。大多數(shù)情況下你可能需要false 最好的知道你的store是否需要設(shè)置resave的方法是通過查看你的store是否實(shí)現(xiàn)了touch方法(刪除那些空閑的session。同時(shí)這個(gè)方法也會(huì)通知session store指定的session是活動(dòng)態(tài)的),如果實(shí)現(xiàn)了那么你可以用resave:false,如果沒有實(shí)現(xiàn)touch方法,同時(shí)你的store對(duì)保存的session設(shè)置了一個(gè)過期的時(shí)間,那么建議你用resave:true

var resaveSession = options.resave; 
 if (resaveSession === undefined) { 
  deprecate('undefined resave option; provide resave option'); 
  resaveSession = true;//如果用戶沒有指定resavedSession那么默認(rèn)就是true 
 } 

我們?cè)賮砜纯雌渌倪壿?/p>

 store.get(req.sessionID, function(err, sess){ 
   // error handling 
   //如果報(bào)錯(cuò)那么也會(huì)創(chuàng)建一個(gè)session 
   if (err) { 
    debug('error %j', err); 
    if (err.code !== 'ENOENT') { 
     next(err); 
     return; 
    } 
    generate(); 
   // no session那么就會(huì)創(chuàng)建一個(gè)session 
   } else if (!sess) { 
    debug('no session found'); 
    generate(); 
   // populate req.session 
   //如果找到了這個(gè)session處理的代碼邏輯 
   } else { 
    debug('session found'); 
    store.createSession(req, sess); 
    originalId = req.sessionID; 
    originalHash = hash(sess); 
    //originalHash保存的是找到的這個(gè)session的hash結(jié)果,如果明確指定了resave為false那么savedHash就是原來的session的結(jié)果 
    if (!resaveSession) { 
     savedHash = originalHash 
    } 
    wrapmethods(req.session); 
   } 
   next(); 
  }); 
 }; 
}; 

其中經(jīng)過了前面的if語句后我們的savedHash就是originalHash,我們看看這個(gè)邏輯在判斷這個(gè)session是否已經(jīng)保存的時(shí)候再次用到了

function isSaved(sess) { 
   return originalId === sess.id && savedHash === hash(sess); 
  } 

rolling:強(qiáng)制在每一個(gè)response中都發(fā)送session標(biāo)識(shí)符的cookie。如果把expiration設(shè)置為一個(gè)過去的時(shí)間那么 那么過期時(shí)間設(shè)置為默認(rèn)的值。roling默認(rèn)是false。如果把這個(gè)值設(shè)置為true但是saveUnitialized設(shè)置為false,那么cookie不會(huì)被包含在響應(yīng)中(沒有初始化的session)

rollingSessions = options.rolling || false;//默認(rèn)為false 

我們看看rolling用于了什么環(huán)境了:

//這個(gè)方法用戶判斷是否需要在請(qǐng)求頭中設(shè)置cookie 
 // determine if cookie should be set on response 
 function shouldSetCookie(req) { 
  // cannot set cookie without a session ID 
  //如果沒有sessionID直接返回,這時(shí)候不用設(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在每一個(gè)響應(yīng)中都應(yīng)該被發(fā)送。也就是說如果用戶設(shè)置了rolling即使sessionID沒有被修改 
   //也依然會(huì)把session的cookie發(fā)送到瀏覽器 
   : rollingSessions || req.session.cookie.expires != null && isModified(req.session); 
 } 

很顯然,如果客戶端發(fā)送的sessionID和服務(wù)器的sessionID一致,如果你指定了rolling為true,那么還是會(huì)發(fā)送這個(gè)session的cookie到客戶端,但是如果你設(shè)置了rolling為false,那么這時(shí)候如果同時(shí)設(shè)置了req.session.cookie.expires,而且這個(gè)req.session被修改了這時(shí)候還是會(huì)把session的cookie發(fā)送到客戶端!

saveUninitialized:強(qiáng)制沒有“初始化”的session保存到storage中,沒有初始化的session指的是:剛被創(chuàng)建沒有被修改,如果是要實(shí)現(xiàn)登陸的session那么最好設(shè)置為false(reducing server storage usage, or complying with laws that require permission before setting a cookie) 而且設(shè)置為false還有一個(gè)好處,當(dāng)客戶端沒有session的情況下并行發(fā)送多個(gè)請(qǐng)求時(shí)。默認(rèn)是true,但是不建議使用默認(rèn)值。

 var saveUninitializedSession = options.saveUninitialized; 
/如果用戶不指定saveUninitializedSession那么提示用戶并設(shè)置saveUninitializedSession為true 
if (saveUninitializedSession === undefined) { 
 deprecate('undefined saveUninitialized option; provide saveUninitialized option'); 
 saveUninitializedSession = true; 
} 

我們來看看這個(gè)參數(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,同時(shí)服務(wù)器的req.sessionID和瀏覽器發(fā)送過來的不一致,這時(shí)候只有在服務(wù)器的session修改的時(shí)候會(huì)保存。如果前面的前提不滿足那么就需要看是否已經(jīng)保存過了,如果沒有保存過那么才會(huì)保存!

這個(gè)參數(shù)還被用于決定是否需要把session的cookie發(fā)送到客戶端:

//這個(gè)方法用戶判斷是否需要在請(qǐng)求頭中設(shè)置cookie 
  // determine if cookie should be set on response 
  function shouldSetCookie(req) { 
   // cannot set cookie without a session ID 
   //如果沒有sessionID直接返回,這時(shí)候不用設(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在每一個(gè)響應(yīng)中都應(yīng)該被發(fā)送。也就是說如果用戶設(shè)置了rolling即使sessionID沒有被修改 
    //也依然會(huì)把session的cookie發(fā)送到瀏覽器 
    : rollingSessions || req.session.cookie.expires != null && isModified(req.session); 
  } 

如果客戶端和服務(wù)器端的sessionID不一致的前提下,如果用戶指定了保存未初始化的session那么就需要發(fā)送,否則就只有在修改的時(shí)候才發(fā)送

secret:用于對(duì)sessionID的cookie進(jìn)行簽名,可以是一個(gè)string(一個(gè)secret)或者數(shù)組(多個(gè)secret)。如果指定了一個(gè)數(shù)組那么只會(huì)用 第一個(gè)元素對(duì)sessionID的cookie進(jìn)行簽名,其他的用于驗(yàn)證請(qǐng)求中的簽名。

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保存的是一個(gè)數(shù)組,即使用戶傳入的僅僅是一個(gè)string 
 if (secret && !Array.isArray(secret)) { 
  secret = [secret]; 
 } 
 //必須提供secret參數(shù) 
 if (!secret) { 
  deprecate('req.secret; provide secret option'); 
 } 

我們看看這個(gè)secret參數(shù)用于什么情景:

//作用:用于從請(qǐng)求對(duì)象request中獲取session ID值,其中name就是我們?cè)趏ptions中指定的,首先從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方法用于從請(qǐng)求中獲取sessionID進(jìn)行解密,作為秘鑰。

// setcookie(res, name, req.sessionID, secrets[0], cookie.data); 
//方法作用:為HTTP響應(yīng)設(shè)置cookie,設(shè)置的cookie是把req.sessionID進(jìn)行加密過后的cookie,其中name用于保存到客戶端的sessionID的cookie的名稱 
function setcookie(res, name, val, secret, options) { 
 var signed = 's:' + signature.sign(val, secret); 
 //對(duì)要發(fā)送的cookie進(jìn)行加密,密鑰為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頭,默認(rèn)是一個(gè)空數(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方法,該方法用于對(duì)sessionID用指定的秘鑰進(jìn)行簽名。

store:保存session的地方,默認(rèn)是一個(gè)MemoryStore實(shí)例

store = options.store || new MemoryStore 
// notify user that this store is not 
// meant for a production environment 
//如果在生產(chǎn)環(huán)境下,同時(shí)store也就是用戶傳入的store(默認(rèn)為MemoryStore)是MemoryStore那么給出警告 
if ('production' == env && store instanceof MemoryStore) { 
 console.warn(warning); 
} 
// generates the new session 
//為用于指定的store添加一個(gè)方法generate,同時(shí)為這個(gè)方法傳入req對(duì)象,在這個(gè)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是否實(shí)現(xiàn)了touch方法 
var storeImplementsTouch = typeof store.touch === 'function'; 
//為store注冊(cè)disconnect事件,在該事件中吧storeReady設(shè)置為false 
store.on('disconnect', function(){ storeReady = false; }); 
//為stroe注冊(cè)connect事件,把storeReady設(shè)置為true 
store.on('connect', function(){ storeReady = true; }); 
 // expose store 
 req.sessionStore = store; 

我們知道這個(gè)store是用于保存session的地方,默認(rèn)是一個(gè)MemoryStore,但是在生產(chǎn)環(huán)境下不建議使用MemoryStore,同時(shí)store有很多自定義的方法,如這里就為他添加了generate,connect,disconnect,當(dāng)然也包含destroy方法。如果你對(duì)store感興趣,可以看看下面這個(gè)通用的store具有的所有的方法:

'use strict'; 
var EventEmitter = require('events').EventEmitter 
 , Session = require('./session') 
 , Cookie = require('./cookie') 
var Store = module.exports = function Store(options){}; 
//這個(gè)Store實(shí)例是一個(gè)EventEmitter實(shí)例,也就是說Store實(shí)例最后還是一個(gè)EventEmitter實(shí)例對(duì)象 
Store.prototype.__proto__ = EventEmitter.prototype; 
 //每一個(gè)store有一個(gè)默認(rèn)的regenerate方法用于產(chǎn)生session 
Store.prototype.regenerate = function(req, fn){ 
 var self = this; 
 //regenerate底層調(diào)用的是destroy方法,第一個(gè)參數(shù)是req.sessionID,至于回調(diào)中的self.generate必須是對(duì)容器進(jìn)行指定的 
 this.destroy(req.sessionID, function(err){ 
  self.generate(req); 
  fn(err);//最后回調(diào)fn 
 }); 
 //調(diào)用這個(gè)store的destory方法,銷毀req.sessionID,銷毀成功后通過剛才的store的generate方法產(chǎn)生一個(gè)sessionID 
}; 
 
//通過指定的sid加載一個(gè)Session實(shí)例,然后觸發(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); 
 }); 
}; 
//從一個(gè)JSON格式的sess中創(chuàng)建一個(gè)session實(shí)例,如sess={cookie:{expires:xx,originalMaxAge:xxx}} 
Store.prototype.createSession = function(req, sess){ 
 var expires = sess.cookie.expires 
  , orig = sess.cookie.originalMaxAge; 
  //創(chuàng)建session時(shí)候獲取其中的cookie域下面的expires,originalMaxAge參數(shù) 
 sess.cookie = new Cookie(sess.cookie); 
 //更新session.cookie為一個(gè)Cookie實(shí)例而不再是一個(gè){}對(duì)象了 
 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)建一個(gè)session實(shí)例,其中傳入的第一個(gè)參數(shù)是req,第二個(gè)參數(shù)是sess也就是我們剛才創(chuàng)建的那個(gè)Cookie實(shí)例,簽名為sess={cookie:cookie對(duì)象} 
 return req.session; 
}; 

unset:對(duì)沒有設(shè)置的req.session進(jìn)行控制,通過delete或者設(shè)置為null。默認(rèn)是keep,destory表示當(dāng)回應(yīng)結(jié)束后會(huì)銷毀session,keep表示session會(huì)被保存。但是在請(qǐng)求中對(duì)session的修改會(huì)被忽略,也不會(huì)保存

//如果用戶指定了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只能是默認(rèn)的destroy或者keep,其用于判斷是否應(yīng)該銷毀session,如果指定了unset方法為destrory,那么就會(huì)銷毀session,也就是把req.session設(shè)置為null

在版本1.5.0后,cookie-parser這個(gè)中間件已經(jīng)不是express-session工作必須的了。這個(gè)模塊可以直接對(duì)req/res中的cookie進(jìn)行讀寫,使用cookie-parser可能導(dǎo)致一些問題,特別是當(dāng)secret在兩個(gè)模塊之間存在不一致的時(shí)候。

請(qǐng)把secure設(shè)置為true,這是明智的。但是這需要網(wǎng)站的支持,因?yàn)閟ecure需要HTTPS的協(xié)議。如果設(shè)置了secure,但是你使用HTTP訪問,那么cookie不會(huì)被設(shè)置,如果Node.js運(yùn)行在代理上,同時(shí)使用了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,同時(shí)在測(cè)試環(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,那么會(huì)按照請(qǐng)求的方式來判斷,如果是安全的就是secure。但是如果網(wǎng)站同時(shí)支持HTTP和HTTPS,這時(shí)候通過HTTPS設(shè)置的cookie

對(duì)于HTTP是不可見的。這在express的”trust proxy“(簡(jiǎn)化開發(fā)和生產(chǎn)環(huán)境)正確設(shè)置的情況下特別有用。默認(rèn)下:cookie.maxAge為null

這意味著,瀏覽器關(guān)閉了這個(gè)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//用這個(gè)屬性獲取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是一個(gè)session對(duì)象,格式如下:

session:    
 //req.session域下面保存的是一個(gè)Session實(shí)例,其中有cookie表示是一個(gè)對(duì)象  
  Session {  
  //這里是req.session.cookie是一個(gè)Cookie實(shí)例  
   cookie:  
   { path: '/',  
    _expires: Fri May 06 2016 15:44:48 GMT+0800 (中國(guó)標(biāo)準(zhǔn)時(shí)間),  
    originalMaxAge: 2591999960,  
    httpOnly: true },  
    flash: { error: [Object]   
   }  
 }  

Session.regenerate():

產(chǎn)生一個(gè)session,調(diào)用這個(gè)方法那么一個(gè)新的SID和Session實(shí)例就會(huì)被創(chuàng)建,同時(shí)放置在req.session中。但是第一步是銷毀指定的session

Store.prototype.regenerate = function(req, fn){ 
 var self = this; 
 //regenerate底層調(diào)用的是destroy方法,第一個(gè)參數(shù)是req.sessionID,至于回調(diào)中的self.generate必須是對(duì)容器進(jìn)行指定的 
 this.destroy(req.sessionID, function(err){ 
  self.generate(req); 
  fn(err);//最后回調(diào)fn 
 }); 
 //調(diào)用這個(gè)store的destory方法,銷毀req.sessionID,銷毀成功后通過剛才的store的generate方法產(chǎn)生一個(gè)sessionID 
}; 

這時(shí)通用store提供的regenerate方法,但是generate方法一般要特定的庫(kù)進(jìn)行輔助:

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); 
 } 
}; 

這時(shí)為express-session為store指定的generate方法

session.destory():

銷毀session,同時(shí)在req.session中被移除,但是在下一次請(qǐng)求的時(shí)候又會(huì)被創(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)容。這個(gè)方法在HTTP的響應(yīng)后自動(dòng)被調(diào)用。如果session中的數(shù)據(jù)被改變了(這個(gè)行為可以通過中間件的很多的配置來改變),正因?yàn)槿绱诉@個(gè)方法一般不用顯示調(diào)用。但是在長(zhǎng)連接的websocket中這個(gè)方法一般需要手動(dòng)調(diào)用

  req.session.save(function(err) { 
 // session saved  
}) 

session.touch():

更新maxAge屬性,一般不需要手動(dòng)調(diào)用,因?yàn)閟ession的中間件已經(jīng)替你調(diào)用了。我們看看Session是如何實(shí)現(xiàn)這個(gè)方法

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仍然存活的時(shí)候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對(duì)象的時(shí)候的初始值。

req.session.id:

唯一的,而且不會(huì)被改變。我們看看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對(duì)象的defineProperty,其中defineProperty用于為這個(gè)對(duì)象指定一個(gè)函數(shù),其中第二個(gè)參數(shù)是函數(shù)的名稱,第三個(gè)是函數(shù)本身 
function defineMethod(obj, name, fn) { 
 Object.defineProperty(obj, name, { 
  configurable: true, 
  enumerable: false, 
  value: fn, 
  writable: true 
 }); 
}; 

其中session的id值就是req.sessionID屬性而且enumerable為false,所以在控制臺(tái)是打印不出來的
req.session.cookie:

每一個(gè)session都有一個(gè)cookie對(duì)象,因此在每一次請(qǐng)求的時(shí)候你都可以改變session的cookie。如我們可以通過req.session.cookie.expires設(shè)置為false,這時(shí)候?yàn)g覽器關(guān)閉cookie就不存在了

Cookie.maxAge:

req.session.cookie.maxAge返回這個(gè)cookie剩余的毫秒數(shù),當(dāng)然我們也可以通過設(shè)置expires來完成

var hour = 3600000 
 req.session.cookie.expires = new Date(Date.now() + hour) 
 req.session.cookie.maxAge = hour//和上面的expires等價(jià) 

當(dāng)maxAge設(shè)置為60000,也就是一分鐘,這時(shí)候如果已經(jīng)過去了30s,那么maxAge就會(huì)返回30000(不過要等到當(dāng)前請(qǐng)求結(jié)束)。如果這時(shí)候我們調(diào)用req.session.touch(),那么req.session.maxAge就成了初始值了60000了

req.sessionID:

只讀的屬性。每一個(gè)session store必須是一個(gè)EventEmitter對(duì)象,同時(shí)要實(shí)現(xiàn)特定的方法。我們看看MemoryStore把:

function MemoryStore() { 
 Store.call(this) 
 this.sessions = Object.create(null) 
} 
//繼承了Store中的所有的原型屬性 
util.inherits(MemoryStore, Store) 

也就是說MemoryStore繼承了通用的Store的所有的屬性和方法,如regenerate,load,createSession,當(dāng)然也實(shí)現(xiàn)了很多自己的方法如all,clear,destroy,get,length,set,touch等

下面討論的是一些其他的方法:

required方法表示:在這個(gè)store上一定會(huì)調(diào)用的方法

Recommended方法表示如果有這個(gè)方法那么在這個(gè)store上就會(huì)調(diào)用。Optional方法表示不會(huì)調(diào)用,但是為了給用戶一個(gè)統(tǒng)一的store!

store.destroy(sid, callback)

必須的方法。通過sessionID來銷毀session,如果session已經(jīng)被銷毀,那么回調(diào)函數(shù)被調(diào)用,同時(shí)傳入一個(gè)error對(duì)象

store.get(sid, callback)

必須的方法。通過sessionID從store中獲取session。回調(diào)函數(shù)是callback(err,session)。如果session存在那么第二個(gè)參數(shù)就是session,否則第二個(gè)參數(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)

推薦的方法。通過一個(gè)指定的sid和session對(duì)象去”接觸“這個(gè)session.如果接觸到了那么回調(diào)為callback(error)。session store用這個(gè)方法去刪除那些空閑的session。同時(shí)這個(gè)方法也會(huì)通知session store指定的session是活動(dòng)態(tài)的。MemoryStore實(shí)現(xiàn)了這個(gè)方法:

//通過指定的sessionId獲取當(dāng)前的session對(duì)象,然后把這個(gè)對(duì)象的cookie更新為新的session對(duì)應(yīng)的cookie,同時(shí)sessions中的當(dāng)前session也進(jìn)行更新(包括過期時(shí)間等) 
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的個(gè)數(shù),回調(diào)函數(shù)為callback(error,length)

store.clear(callback)

可選的方法,從store中吧所有的session都刪除,回調(diào)函數(shù)為callback(err)

store.all(callback)

可選的方法。以一個(gè)數(shù)組的方法獲取store中的sessions。callback(error,sessions)

session({ 
  secret: settings.cookieSecret, 
  //blog=s%3AisA3_M-Vso0L_gHvUnPb8Kw9DohpCCBJ.OV7p42pL91uM3jueaJATpZdlIj%2BilgxWoD8HmBSLUSo 
  //其中secret如果是一個(gè)string,那么就是用這個(gè)string對(duì)sessionID對(duì)應(yīng)的cookie進(jìn)行簽名,如果是一個(gè)數(shù)組那么只有第一個(gè)用于簽名,其他用于瀏覽器請(qǐng)求后的驗(yàn)證 
  key: settings.db, 
  //設(shè)置的cookie的名字,從上面可以看到這里指定的是blog,所以瀏覽器的請(qǐng)求中可以看到這里的sessionID已經(jīng)不是sessionID了,而是這里的blog 
  name:"qinliang",//name的優(yōu)先級(jí)比key要高,如果同時(shí)設(shè)置了那么就是按照name來制定的 
  //沒有name時(shí)候response中為:set-cookie:blog=s%3A6OJEWycwVMmTGXcZqawrW0HNLOTJkYKm.0Slax72TMfW%2B4Tiit3Ox7NAj5S6rPWvMUr6sY02l0DE; Path=/; Expires=Thu, 28 Apr 2016 10:47:13 GMT; HttpOnly 
  //當(dāng)有name的時(shí)候resopnse中:set-cookie:qinliang=s%3ABDOjujVhV0DH9Atax_gl4DgZ4-1RGvjQ.OeUddoRalzB4iSmUHcE8oMziad4Ig7jUT1REzGcYcdg; Path=/; Expires=Thu, 28 Apr 2016 10:48:26 GMT; HttpOnly 
  resave:true,//沒有實(shí)現(xiàn)touch方法,同時(shí)也設(shè)置了session的過期時(shí)間為30天 
  rolling:true,//如果設(shè)置了rolling為true,同時(shí)saveUninitialized為true,那么每一個(gè)請(qǐng)求都會(huì)發(fā)送沒有初始化的session! 
  saveUninitialized:false,//設(shè)置為true,存儲(chǔ)空間浪費(fèi),不允許權(quán)限管理 
  cookie:  
  { 
    maxAge: 1000 * 60 * 60 * 24 * 30 
   }, 
  //cookie里面全部的設(shè)置都是對(duì)于sessionID的屬性的設(shè)置,默認(rèn)的屬性為{ path: '/', httpOnly: true, secure: false, maxAge: null }. 
  //所以最后我們保存到數(shù)據(jù)庫(kù)里面的信息就是:{"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 
  }) 
}) 

從源碼的角度來分析配置項(xiàng):

(1)這里面的secret到底有什么用呢?

我們看看這個(gè)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進(jìn)行的解密操作

// 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; 
} 

通過這里我們很容易看到對(duì)于session ID的獲取就是通過上面的secret進(jìn)行簽名的,如果獲取到的sessionID已經(jīng)被修改過,那么表示這個(gè)session已經(jīng)無效了。首先是從req.headers.cookie中獲取,然后從req.signedCookies中獲取,最后從req.cookies中進(jìn)行獲取!

(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)生一個(gè)sessionID 
  req.session = new Session(req);//產(chǎn)生一個(gè)Session 
  req.session.cookie = new Cookie(cookieOptions);//在req.session對(duì)象的cookie域下面保存的是一個(gè)Cookie對(duì)象 
  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就是這個(gè)新創(chuàng)建的Cookie具有這些默認(rèn)的屬性,同時(shí)還具有用戶自己傳入的options參數(shù),如用戶傳入的var cookieOptions = options.cookie || {}; 
 //也就是用戶傳入的options.cookie屬性 
 if (options) merge(this, options); 
 /*這個(gè)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; 
 //默認(rèn)的originalMaxAge就是this.maxAge也就是null,如果指定了originalMaxAge那么就是用戶指定的值 
}; 

也就是說我們?cè)趕ession中傳入的cookie參數(shù)也成為新創(chuàng)建的cookie的一個(gè)屬性了,而且這個(gè)這個(gè)新創(chuàng)建的cookie被保存到req.session.cookie下。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI