node.js中express-session配置項詳解


官方地址:https://www.npmjs.com/package/express-session

 

作用:用指定的參數創建一個session中間件,sesison數據不是保存在cookie中,僅僅sessionID保存到cookie中,session的數據僅僅保存在服務器端
警告:默認的服務器端的session存儲,MemoryStore不是為了生產環境創建的,大多數情況下會內存泄露,主要用於測試和開發環境
接受的參數:

   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:產生一個新的sessionID的函數,一個返回值是string類型的函數會被作為sessionID.這個函數第一個參數是req,所以如果你想要req中的參數產生sessionID還是很不錯的
默認函數是使用uid-safe這個庫產生id值(產生一個算法上安全的UID,可以用於cookie也可以用於URL。和rand-token和uid2相比,后者由於使用了%導致UID產生偏態,同時可能對UID產生不必要的截斷。我們的uid-safe使用的是base64算法,其函數uid(byteLength, callback)中第一個參數是比特長度而不是字符串長度)

    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參數那么就是默認使用generateSessionId函數來完成

 name:在response中sessionID這個cookie的名稱。也可以通過這個name讀取,默認是connect.sid。如果一台機器上有多個app運行在同樣的hostname+port, 那么你需要對這個sessin的cookie進行切割,所以最好的方法還是通過name設置不同的值

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

  resave:強制session保存到session store中。即使在請求中這個session沒有被修改。但是這個並不一定是必須的,如果客戶端有兩個並行的請求到你的客戶端,一個請求對session的修改可能被另外一個請求覆蓋掉,即使第二個請求並沒有修改sesion。默認是true,但是默認值已經過時,因此以后default可能會被修改。因此好好研究你的需求選擇一個最適用的。大多數情況下你可能需要false 最好的知道你的store是否需要設置resave的方法是通過查看你的store是否實現了touch方法(刪除那些空閑的session。同時這個方法也會通知session store指定的session是活動態的),如果實現了那么你可以用resave:false,如果沒有實現touch方法,同時你的store對保存的session設置了一個過期的時間,那么建議你用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
      //如果報錯那么也會創建一個session
      if (err) {
        debug('error %j', err);
        if (err.code !== 'ENOENT') {
          next(err);
          return;
        }
        generate();
      // no session那么就會創建一個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結果,如果明確指定了resave為false那么savedHash就是原來的session的結果
        if (!resaveSession) {
          savedHash = originalHash
        }
        wrapmethods(req.session);
      }
      next();
    });
  };
};

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

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

rolling:強制在每一個response中都發送session標識符的cookie。如果把expiration設置為一個過去的時間那么 那么過期時間設置為默認的值。roling默認是false。如果把這個值設置為true但是saveUnitialized設置為false,那么cookie不會被包含在響應中(沒有初始化的session)

 rollingSessions = options.rolling || false;//默認為false

我們看看rolling用於了什么環境了:

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

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

 

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

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

我們來看看這個參數用於做什么判斷,首先看看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,同時服務器的req.sessionID和瀏覽器發送過來的不一致,這時候只有在服務器的session修改的時候會保存。如果前面的前提不滿足那么就需要看是否已經保存過了,如果沒有保存過那么才會保存!
這個參數還被用於決定是否需要把session的cookie發送到客戶端:

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

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

 

  secret:用於對sessionID的cookie進行簽名,可以是一個string(一個secret)或者數組(多個secret)。如果指定了一個數組那么只會用 第一個元素對sessionID的cookie進行簽       名,其他的用於驗證請求中的簽名。

var secret = options.secret;
  //unsetDestroy表示用戶是否指定了unset參數是destroy,是布爾值
  if (Array.isArray(secret) && secret.length === 0) {
    throw new TypeError('secret option array must contain one or more strings');
  }
  //保證secret保存的是一個數組,即使用戶傳入的僅僅是一個string
  if (secret && !Array.isArray(secret)) {
    secret = [secret];
  }
 //必須提供secret參數
  if (!secret) {
    deprecate('req.secret; provide secret option');
  }

我們看看這個secret參數用於什么情景:

//作用:用於從請求對象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響應設置cookie,設置的cookie是把req.sessionID進行加密過后的cookie,其中name用於保存到客戶端的sessionID的cookie的名稱
function setcookie(res, name, val, secret, options) {
  var signed = 's:' + signature.sign(val, secret);
  //對要發送的cookie進行加密,密鑰為secret
  var data = cookie.serialize(name, signed, options);
  //其中options中可能有decode函數,返回序列化的cookie
  debug('set-cookie %s', data);
  var prev = res.getHeader('set-cookie') || [];
  //獲取set-cookie頭,默認是一個空數組
  var header = Array.isArray(prev) ? prev.concat(data)
    : Array.isArray(data) ? [prev].concat(data)
    : [prev, data];
  //通過set-cookie,發送到客戶端
  res.setHeader('set-cookie', header)
}

用於setcookie方法,該方法用於對sessionID用指定的秘鑰進行簽名。不懂簽名的概念可以閱讀阿里巴巴攻城師分享nodeJS精華:cookie 和 session,也可查看我的另外一篇博客 Cookie-Parser是如何解析簽名后的cookie的(同時對cookie和cookie-signature進行說明)

 

  store:保存session的地方,默認是一個MemoryStore實例

  store = options.store || new MemoryStore
  // notify user that this store is not
  // meant for a production environment
  //如果在生產環境下,同時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參數如果是auto,那么修改req.session.cookie的secure參數,並通過issecure來判斷
    if (cookieOptions.secure === 'auto') {
      req.session.cookie.secure = issecure(req, trustProxy);
    }
  };
  //查看store是否實現了touch方法
  var storeImplementsTouch = typeof store.touch === 'function';
  //為store注冊disconnect事件,在該事件中吧storeReady設置為false
  store.on('disconnect', function(){ storeReady = false; });
  //為stroe注冊connect事件,把storeReady設置為true
  store.on('connect', function(){ storeReady = true; });
    // expose store
    req.sessionStore = store;

我們知道這個store是用於保存session的地方,默認是一個MemoryStore,但是在生產環境下不建議使用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方法用於產生session
Store.prototype.regenerate = function(req, fn){
  var self = this;
  //regenerate底層調用的是destroy方法,第一個參數是req.sessionID,至於回調中的self.generate必須是對容器進行指定的
  this.destroy(req.sessionID, function(err){
    self.generate(req);
    fn(err);//最后回調fn
  });
  //調用這個store的destory方法,銷毀req.sessionID,銷毀成功后通過剛才的store的generate方法產生一個sessionID
};

//通過指定的sid加載一個Session實例,然后觸發函數fn(err,sess)
Store.prototype.load = function(sid, fn){
  var self = this;
  //最后調用的是Store的get方法
  this.get(sid, function(err, sess){
    if (err) return fn(err);
    if (!sess) return fn();
    //如果sess為空那么調用fn()方法
    var req = { sessionID: sid, sessionStore: self };
    //調用createSession來完成的
    sess = self.createSession(req, sess);
    fn(null, sess);
  });
};
//從一個JSON格式的sess中創建一個session實例,如sess={cookie:{expires:xx,originalMaxAge:xxx}}
Store.prototype.createSession = function(req, sess){
  var expires = sess.cookie.expires
    , orig = sess.cookie.originalMaxAge;
    //創建session時候獲取其中的cookie域下面的expires,originalMaxAge參數
  sess.cookie = new Cookie(sess.cookie);
  //更新session.cookie為一個Cookie實例而不再是一個{}對象了
  if ('string' == typeof expires) sess.cookie.expires = new Date(expires);
  sess.cookie.originalMaxAge = orig;
  //為新構建的cookie添加originalMaxAge屬性
  req.session = new Session(req, sess);
  //創建一個session實例,其中傳入的第一個參數是req,第二個參數是sess也就是我們剛才創建的那個Cookie實例,簽名為sess={cookie:cookie對象}
  return req.session;
};

unset:對沒有設置的req.session進行控制,通過delete或者設置為null。默認是keep,destory表示當回應結束后會銷毀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已經被銷毀了
    function shouldDestroy(req) {
      //  var unsetDestroy = options.unset === 'destroy';
      return req.sessionID && unsetDestroy && req.session == null;
    }

我們可以看到unset只能是默認的destroy或者keep,其用於判斷是否應該銷毀session,如果指定了unset方法為destrory,那么就會銷毀session,也就是把req.session設置為null

 

在版本1.5.0后,cookie-parser這個中間件已經不是express-session工作必須的了。這個模塊可以直接對req/res中的cookie進行讀寫,使用cookie-parser可能導致一些問題,特別是當secret在兩個模塊之間存在不一致的時候。
請把secure設置為true,這是明智的。但是這需要網站的支持,因為secure需要HTTPS的協議。如果設置了secure,但是你使用HTTP訪問,那么cookie不會被設置,如果node.js運行在代理上,同時使用了secure:true那么在express中
需要設置”信任代理“。

var app = express()
app.set('trust proxy', 1) // trust first proxy 
app.use(session({
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: true,
  cookie: { secure: true }
}))

如果在生產環境下需要使用安全的cookit,同時在測試環境也要能夠使用。那么可以使用express中的NODE_ENV參數

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屬性可以設置為auto,那么會按照請求的方式來判斷,如果是安全的就是secure。但是如果網站同時支持HTTP和HTTPS,這時候通過HTTPS設置的cookie
對於HTTP是不可見的。這在express的”trust proxy“(簡化開發和生產環境)正確設置的情況下特別有用。默認下:cookie.maxAge為null
這意味着,瀏覽器關閉了這個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中保存的數據,而且返回的JSON數據
  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():

產生一個session,調用這個方法那么一個新的SID和Session實例就會被創建,同時放置在req.session中。但是第一步是銷毀指定的session

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

這時通用store提供的regenerate方法,但是generate方法一般要特定的庫進行輔助:

  store.generate = function(req){
    req.sessionID = generateId(req);
    req.session = new Session(req);
    req.session.cookie = new Cookie(cookieOptions);
    //用戶指定的secure參數如果是auto,那么修改req.session.cookie的secure參數,並通過issecure來判斷
    if (cookieOptions.secure === 'auto') {
      req.session.cookie.secure = issecure(req, trustProxy);
    }
  };

這時為express-session為store指定的generate方法

 

session.destory():
   銷毀session,同時在req.session中被移除,但是在下一次請求的時候又會被創建

       req.session.destroy(function(err) {
      // cannot access session here 
    })

session.reload():
    重新裝載session中的數據

        req.session.reload(function(err) {
      // session updated 
    })

session.save():
   把session中的數據重新保存到store中,用內存的內容去替換掉store中的內容。這個方法在HTTP的響應后自動被調用。如果session中的數據被改變了(這個行為可以通過中間件的很多的配置來改變),正因為如此這個方法一般不用顯示調用。但是在長連接的websocket中這個方法一般需要手動調用 

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

session.touch():

   更新maxAge屬性,一般不需要手動調用,因為session的中間件已經替你調用了。我們看看Session是如何實現這個方法

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已經過期了
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設置為構造Session對象的時候的初始值。

 

req.session.id:

   唯一的,而且不會被改變。我們看看Session的構造函數就明白了:

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用於為這個對象指定一個函數,其中第二個參數是函數的名稱,第三個是函數本身
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設置為false,這時候瀏覽器關閉cookie就不存在了
Cookie.maxAge:
   req.session.cookie.maxAge返回這個cookie剩余的毫秒數,當然我們也可以通過設置expires來完成

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

當maxAge設置為60000,也就是一分鍾,這時候如果已經過去了30s,那么maxAge就會返回30000(不過要等到當前請求結束)。如果這時候我們調用req.session.touch(),那么req.session.maxAge就成了初始值了60000了
req.sessionID:

  只讀的屬性。每一個session store必須是一個EventEmitter對象,同時要實現特定的方法。我們看看MemoryStore把:

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

也就是說MemoryStore繼承了通用的Store的所有的屬性和方法,如regenerate,load,createSession,當然也實現了很多自己的方法如all,clear,destroy,get,length,set,touch等
下面討論的是一些其他的方法:

required方法表示:在這個store上一定會調用的方法

Recommended方法表示如果有這個方法那么在這個store上就會調用。Optional方法表示不會調用,但是為了給用戶一個統一的store!

store.destroy(sid, callback)
  必須的方法。通過sessionID來銷毀session,如果session已經被銷毀,那么回調函數被調用,同時傳入一個error對象
store.get(sid, callback)
必須的方法。通過sessionID從store中獲取session。回調函數是callback(err,session)。如果session存在那么第二個參數就是session,否則第二個參數就是null/undefined。如果error.code==="ENOENT"那么回調為callback(null,null)
store.set(sid, session, callback)
必須的方法。如果被成功設置了那么回調為callback(error)
store.touch(sid, session, callback)
推薦的方法。通過一個指定的sid和session對象去”接觸“這個session.如果接觸到了那么回調為callback(error)。session store用這個方法去刪除那些空閑的session。同時這個方法也會通知session store指定的session是活動態的。MemoryStore實現了這個方法:

//通過指定的sessionId獲取當前的session對象,然后把這個對象的cookie更新為新的session對應的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的個數,回調函數為callback(error,length)
store.clear(callback)
 可選的方法,從store中吧所有的session都刪除,回調函數為callback(err)
store.all(callback)
 可選的方法。以一個數組的方法獲取store中的sessions。callback(error,sessions)

session({
    secret: settings.cookieSecret,
    //blog=s%3AisA3_M-Vso0L_gHvUnPb8Kw9DohpCCBJ.OV7p42pL91uM3jueaJATpZdlIj%2BilgxWoD8HmBSLUSo
    //其中secret如果是一個string,那么就是用這個string對sessionID對應的cookie進行簽名,如果是一個數組那么只有第一個用於簽名,其他用於瀏覽器請求后的驗證
    key: settings.db,
    //設置的cookie的名字,從上面可以看到這里指定的是blog,所以瀏覽器的請求中可以看到這里的sessionID已經不是sessionID了,而是這里的blog
    name:"qinliang",//name的優先級比key要高,如果同時設置了那么就是按照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,//沒有實現touch方法,同時也設置了session的過期時間為30天
    rolling:true,//如果設置了rolling為true,同時saveUninitialized為true,那么每一個請求都會發送沒有初始化的session!
    saveUninitialized:false,//設置為true,存儲空間浪費,不允許權限管理
    cookie: 
    {
        maxAge: 1000 * 60 * 60 * 24 * 30
     },
    //cookie里面全部的設置都是對於sessionID的屬性的設置,默認的屬性為{ path: '/', httpOnly: true, secure: false, maxAge: null }.
    //所以最后我們保存到數據庫里面的信息就是:{"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到底有什么用呢?

首先我建議你讀一下Cookie-Parser是如何解析簽名后的cookie的(同時對cookie和cookie-signature進行說明),然后我們看看這個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的數據,那么就去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中也沒有獲取到數據那么直接從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已經被修改過,那么表示這個session已經無效了。首先是從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);//產生一個sessionID
    req.session = new Session(req);//產生一個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就是這個新創建的Cookie具有這些默認的屬性,同時還具有用戶自己傳入的options參數,如用戶傳入的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參數也成為新創建的cookie的一個屬性了,而且這個這個新創建的cookie被保存到req.session.cookie下。

 原文地址:http://blog.csdn.net/liangklfang/article/details/50998959


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM