node.js中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
r 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用指定的秘钥进行签名。

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到底有什么用呢?

我们看看这个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下。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Nodejs进阶:express+session实现简易登录身份认证

    文档概览 本文基于express.express-session实现了简易的登录/登出功能,完整的代码示例可以在这里找到. 环境初始化 首先,初始化项目 express -e 然后,安装依赖. npm install 接着,安装session相关的包. npm install --save express-session session-file-store session相关配置 配置如下,并不复杂,可以见代码注释,或者参考官方文档. var express = require('expres

  • 详解nodejs express下使用redis管理session

    Session实现原理 实现请求身份验证的方式很多,其中一种广泛接受的方式是使用服务器端产生的Session ID结合浏览器的Cookie实现对Session的管理,一般来说包括以下4个步骤: 1.服务器端的产生Session ID 2.服务器端和客户端存储Session ID 3.从HTTP Header中提取Session ID 4.根据Session ID从服务器端的Hash中获取请求者身份信息 使用Express和Redis对Session管理的实现 var session = requ

  • 详解Node.js开发中的express-session

    什么是session session是保存在服务器端的会话.session的典型应用场景是用户登录某网站之后,将其登录信息放入session,在以后的每次请求中查询相应的登录信息以确保该用户合法.比如购物车等等经典场景 为什么要使用session 谈及session一般是在web应用的背景之下,我们知道web应用是基于HTTP协议的,而HTTP协议恰恰是一种无状态协议.也就是说,用户从A页面跳转到B页面会重新发送一次HTTP请求,而服务端在返回响应的时候是无法获知该用户在请求B页面之前做了什么的

  • node.js 中间件express-session使用详解

    本文介绍的关于node.js中间件express-session的相关内容,分享出来供大家从参考学习,下面来一起看看详细的介绍: 一.为什么使用session? session运行在服务器端,当客户端第一次访问服务器时,可以将客户的登录信息保存. 当客户访问其他页面时,可以判断客户的登录状态,做出提示,相当于登录拦截. session可以和Redis或者数据库等结合做持久化操作,当服务器挂掉时也不会导致某些客户信息(购物车)丢失. 二.session的工作流程: 当浏览器访问服务器并发送第一次请

  • 详解node.js平台下Express的session与cookie模块包的配置

    首先下载两个模块包 session模块包:用于保持登录状态或保持会话状态等. npm install express-session --save-dev cookie模块包:用于解析cookie. npm install cookie-parser --save-dev 接着在app.js(我在node.js的配置中提到的,也就是服务器主文件)中配置: var session = require("express-session"); var cookie = require(&qu

  • 在Node.js中使用Javascript Generators详解

    Generators是Javascript的一种协同程序( coroutine 简称:协程)风格,是指那些可以在执行时暂停然后又恢复的函数,该函数是在functi配以星号符号形式如function* ,函数内有些特征关键词如yield 和yield*. function* generatorFn () { console.log('look ma I was suspended') } var generator = generatorFn() // [1] setTimeout(functio

  • Node.js中的child_process模块详解

    前言 本文主要给大家介绍了关于Node.js中child_process模块的相关内容,在介绍child_process模块之前,先来看一个例子. const http = require('http'); const longComputation = () => { let sum = 0; for (let i = 0; i < 1e10; i++) { sum += i; }; return sum; }; const server = http.createServer(); ser

  • node.js中的require使用详解

    代码注释里已经描述的非常的清晰,这里就不多废话了,直接奉上代码: 复制代码 代码如下: /*在node中,可以使用require()函数来加载模块.  * require函数使用一个参数,参数值可以带有完整路径的模块的文件名,也可以为模块名.当使用node中提供的模块时,在require函数中只需要指定模块名即可.  * */ //建立一个页面2.js;代码如下 var name="思思博士"; exports.name=name; //建立一个页面1.js;代码如下 var two=

  • node.js中的事件处理机制详解

    EventEmitter类 在Node.js的用于实现各种事件处理的event模块中,定义了一个EventEmitter类.所有可能触发事件的对象都是一个集成了EventEmitter类的子类的实例对象,在Node.js中,为EventEmitter类定义了许多方法,所有与对象的事件处理函数的绑定及解除相关的处理均依靠这些方法的调用来执行. EventEmitter类的各种方法 event:代表事件名 listener:代表事件处理函数 中括号内的参数代表该参数为可选参数 方法名与参数 描述 a

  • Node.js中的事件驱动编程详解

    在传统程编程模里,I/O操作就像一个普通的本地函数调用:在函数执行完之前程序被堵塞,无法继续运行.堵塞I/O起源于早先的时间片模型,这种模型下每个进程就像一个独立的人,目的是将每个人区分开,而且每个人在同一时刻通常只能做一件事,必须等待前面的事做完才能决定下一件事做什么.但是这种在计算机网络和Internet上被广泛使用的"一个用户,一个进程"的模型伸缩性很差.管理多个进程时,会耗费很多内存,上下文切换也会占用大量资源,这些对操作系统是个很大的负担,而且随着进程数的递增,会导致系统性能

  • Node.js中Express框架的使用教程详解

    目录 Express简介 Express生成器 1. 什么是Express生成器 2. 安装Express生成器 创建Express项目 安装项目依赖 运行Express项目 Express目录结构说明 创建一个新路由模块 1. 创建ejs模块文件 2. 创建路由文件 3. 在app.js中引入路由 4. 运行项目 经过前面几天对Node.js的学习,基本的开发知识已经逐渐掌握,所谓工欲善其事必先利其器,今天进一步学习Node.js的开发框架Express.利用Express框架可以快速的进行W

  • 开箱即用的Node.js+Mysql模块封装实现详解

    目录 前言 正文 项目目录 sql.js dbconfig.js db.js 增 删 改 查一个 查全部 paramList 如何使用 搞定 前言 最近在写毕设,采用的是学弟+自己的技术栈,运用Vue3+ElementPlus搞前端,Node.js express做后端,毕竟,java那东西确实不在我技术栈里. 于是乎,我抱着能CV就不要自己敲的心态,前去了某C站,C回了一个封装好了看上去存在可用性的基础sql模块,结果,踩了大坑. 痛定思痛,我采用了学弟+腾讯会议的debug方式,于事发当天下

  • node.js连接mongoose数据库方法详解

    目录 创建项目命令 连接mongoose数据库 配置跨域 运行 创建项目命令 express --view ejs 项目名 进入项目,我们需要npm i 下载配置文件 有时候会出先端口已经被占用的情况,我们可以在bin目录下www文件中修改端口 连接mongoose数据库 我们创建一个新的文件夹,然后在文件夹下创建一个连接mongoose数据库的文件,代码如下 var mongoose=require('mongoose') mongoose.connect('mongodb://127.0.0

  • Node.js REPL (交互式解释器)实例详解

    Node.js  REPL (交互式解释器)实例详解 Node.js REPL(Read Eval Print Loop:交互式解释器) 表示一个电脑的环境,类似 Window 系统的终端,我们可以在终端中输入命令,并接收系统的响应. Node 自带了交互式解释器,可以执行以下任务: 读取 - 读取用户输入,解析输入了Javascript 数据结构并存储在内存中. 执行 - 执行输入的数据结构 打印 - 输出结果 循环 - 循环操作以上步骤直到用户两次按下 ctrl-c 按钮退出. 多行表达式

  • node.js命令行教程图文详解

    本文先介绍原生的node.js实现命令行交互,了解原生的api,然后通过commander.js和inquirer.js实现一个完整的交互命令行工具. 项目地址 process (进程) process对象是一个全局变量,它提供了当前node.js进程的信息并对其控制.因为其是一个全局变量所以无需在文件中引入. 需要用到的几个api process.argv process.cwd() process.stdin process.stdout process.stdin.resume() pro

随机推荐