Home >Web Front-end >JS Tutorial >Detailed explanation of express-session configuration items in node.js
This article mainly introduces the detailed explanation of the express-session configuration items in node.js. The editor thinks it is quite good, so I will share it with you now. Also as a reference for everyone. Let’s follow the editor and take a look.
Official address: Read
Function: Create a sessionmiddleware with specified parameters. The session data is not saved in cookie, only the sessionID is saved in the cookie, and the session data is only saved on the server side
Warning: The default server-side session storage, MemoryStore, is not created for the production environment. Memory leaks occur in most cases, mainly used in testing and development environments
Accepted parameters:
cookie:That is the cookie of session ID, the default is { 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:Generate a new sessionID function , a function whose return value is string type will be used as sessionID. The first parameter of this function is req, so if you want to req It is still very good to use the parameters to generate sessionID
The default function is to use the uid-safe library to generate the id value (generate an algorithmically safe UID, which can be used for cookies or Used for URLs. Compared with rand-token and uid2, the latter causes UID to be skewed due to the use of %, and may cause unnecessary truncation of UID. Our uid-safe uses the base64 algorithm, and its function uid( byteLength, callback) the first parameter is the bit length instead of stringlength)
app.use(session({ genid: function(req) { return genuuid() // use UUIDs for session IDs }, secret: 'keyboard cat' })
Source code snippet:
function generateSessionId(sess) { return uid(24); } var generateId = options.genid || generateSessionId; //如果用户没有传入genid参数那么就是默认使用generateSessionId函数来完成
name:The name of the cookie sessionID in response. It can also be read through this name. The default is connect.sid. If there are multiple apps running on the same hostname+port on a machine, then you need to cut the sessin cookie, so the best way is to set different values through name
name = options.name || options.key || 'connect.sid' //很显然cookie的name默认是connect.sid,而且首先获取到的name而不是key r cookieId = req.sessionID = getcookie(req, name, secrets);
resave:Force the session to be saved in the session store. Even though the session has not been modified during the request. But this is not necessarily necessary. If the client has two parallel requests to your client, the modification of the session by one request may be overwritten by another request, even if the second request does not modify the session. The default is true, but the default value is obsolete, so the default may be modified in the future. So research your needs carefully and choose the one that best suits your needs. In most cases you may need false. The best way to know whether your store needs to set resave is to see if your store implements the touch method (Deletethose idle sessions. At the same time, this method will also Notify the session store that the specified session is active). If it is implemented, you can use resave:false. If the touch method is not implemented and your store sets an expiration time for the saved session, it is recommended that you use resave: true
var resaveSession = options.resave; if (resaveSession === undefined) { deprecate('undefined resave option; provide resave option'); resaveSession = true;//如果用户没有指定resavedSession那么默认就是true }
Let’s take a look at other logic
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(); }); }; };
After passing the previous if statement, our savedHash is the originalHash. Let’s look at this logic when judging whether the session has been saved.
function isSaved(sess) { return originalId === sess.id && savedHash === hash(sess); }
rolling: is used again to force the session identifier cookie to be sent in every response. If expiration is set to a time in the past, then the expiration time is set to the default value. rolling defaults to false. If this value is set to true but saveUnitialized is set to false, the cookie will not be included in the response (no initialized session)
rollingSessions = options.rolling || false;//默认为false
Let's see what environment rolling is used for:
//这个方法用户判断是否需要在请求头中设置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); }
Obviously, if the sessionID sent by the client is consistent with the sessionID of the server, and if you specify rolling as true, then the cookie of this session will still be sent to the client, but if you set rolling as false, then at this time If req.session.cookie.expires is set at the same time, and the req.session is modified, the session cookie will still be sent to the client!
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下。
The above is the detailed content of Detailed explanation of express-session configuration items in node.js. For more information, please follow other related articles on the PHP Chinese website!