node.js中express-session配置项详解
官方地址:阅读
作用:用指定的参数创建一个session中间件,sesison数据不是保存在cookie中,仅仅sessionID保存到cookie中,session的数据仅仅保存在服务器端
警告:默认的服务器端的session存储,MemoryStore不是为了生产环境创建的,大多数情况下会内存泄露,主要用于测试和开发环境
接受的参数:
cookie:也就是sessionID的cookie,默认是{path:'/',httpOnly:true,secure:false,maxAge:null}.
varCookie=module.exports=functionCookie(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){
returngenuuid()//useUUIDsforsessionIDs
},
secret:'keyboardcat'
})
源码片段:
functiongenerateSessionId(sess){
returnuid(24);
}
vargenerateId=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 rcookieId=req.sessionID=getcookie(req,name,secrets);
resave:强制session保存到sessionstore中。即使在请求中这个session没有被修改。但是这个并不一定是必须的,如果客户端有两个并行的请求到你的客户端,一个请求对session的修改可能被另外一个请求覆盖掉,即使第二个请求并没有修改sesion。默认是true,但是默认值已经过时,因此以后default可能会被修改。因此好好研究你的需求选择一个最适用的。大多数情况下你可能需要false最好的知道你的store是否需要设置resave的方法是通过查看你的store是否实现了touch方法(删除那些空闲的session。同时这个方法也会通知sessionstore指定的session是活动态的),如果实现了那么你可以用resave:false,如果没有实现touch方法,同时你的store对保存的session设置了一个过期的时间,那么建议你用resave:true
varresaveSession=options.resave;
if(resaveSession===undefined){
deprecate('undefinedresaveoption;provideresaveoption');
resaveSession=true;//如果用户没有指定resavedSession那么默认就是true
}
我们再来看看其他的逻辑
store.get(req.sessionID,function(err,sess){
//errorhandling
//如果报错那么也会创建一个session
if(err){
debug('error%j',err);
if(err.code!=='ENOENT'){
next(err);
return;
}
generate();
//nosession那么就会创建一个session
}elseif(!sess){
debug('nosessionfound');
generate();
//populatereq.session
//如果找到了这个session处理的代码逻辑
}else{
debug('sessionfound');
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是否已经保存的时候再次用到了
functionisSaved(sess){
returnoriginalId===sess.id&&savedHash===hash(sess);
}
rolling:强制在每一个response中都发送session标识符的cookie。如果把expiration设置为一个过去的时间那么那么过期时间设置为默认的值。roling默认是false。如果把这个值设置为true但是saveUnitialized设置为false,那么cookie不会被包含在响应中(没有初始化的session)
rollingSessions=options.rolling||false;//默认为false
我们看看rolling用于了什么环境了:
//这个方法用户判断是否需要在请求头中设置cookie
//determineifcookieshouldbesetonresponse
functionshouldSetCookie(req){
//cannotsetcookiewithoutasessionID
//如果没有sessionID直接返回,这时候不用设置cookie
if(typeofreq.sessionID!=='string'){
returnfalse;
}
//varcookieId=req.sessionID=getcookie(req,name,secrets);
returncookieId!=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(reducingserverstorageusage,orcomplyingwithlawsthatrequirepermissionbeforesettingacookie)而且设置为false还有一个好处,当客户端没有session的情况下并行发送多个请求时。默认是true,但是不建议使用默认值。
varsaveUninitializedSession=options.saveUninitialized;
/如果用户不指定saveUninitializedSession那么提示用户并设置saveUninitializedSession为true
if(saveUninitializedSession===undefined){
deprecate('undefinedsaveUninitializedoption;providesaveUninitializedoption');
saveUninitializedSession=true;
}
我们来看看这个参数用于做什么判断,首先看看shouldSave方法
//determineifsessionshouldbesavedtostore
//判断是否需要把session保存到到store中
functionshouldSave(req){
//cannotsetcookiewithoutasessionID
if(typeofreq.sessionID!=='string'){
debug('sessionignoredbecauseofbogusreq.sessionID%o',req.sessionID);
returnfalse;
}
//varsaveUninitializedSession=options.saveUninitialized;
//varcookieId=req.sessionID=getcookie(req,name,secrets);
return!saveUninitializedSession&&cookieId!==req.sessionID
?isModified(req.session)
:!isSaved(req.session)
}
如果用户指明了不能保存未初始化的session,同时服务器的req.sessionID和浏览器发送过来的不一致,这时候只有在服务器的session修改的时候会保存。如果前面的前提不满足那么就需要看是否已经保存过了,如果没有保存过那么才会保存!
这个参数还被用于决定是否需要把session的cookie发送到客户端:
//这个方法用户判断是否需要在请求头中设置cookie
//determineifcookieshouldbesetonresponse
functionshouldSetCookie(req){
//cannotsetcookiewithoutasessionID
//如果没有sessionID直接返回,这时候不用设置cookie
if(typeofreq.sessionID!=='string'){
returnfalse;
}
//varcookieId=req.sessionID=getcookie(req,name,secrets);
returncookieId!=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进行签名,其他的用于验证请求中的签名。
varsecret=options.secret;
//unsetDestroy表示用户是否指定了unset参数是destroy,是布尔值
if(Array.isArray(secret)&&secret.length===0){
thrownewTypeError('secretoptionarraymustcontainoneormorestrings');
}
//保证secret保存的是一个数组,即使用户传入的仅仅是一个string
if(secret&&!Array.isArray(secret)){
secret=[secret];
}
//必须提供secret参数
if(!secret){
deprecate('req.secret;providesecretoption');
}
我们看看这个secret参数用于什么情景:
//作用:用于从请求对象request中获取sessionID值,其中name就是我们在options中指定的,首先从req.headers.cookie获取,接着从req.signedCookies中获取,最后从req.cookies获取
functiongetcookie(req,name,secrets){
varheader=req.headers.cookie;
varraw;
varval;
//readfromcookieheader
if(header){
varcookies=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('cookiesignatureinvalid');
val=undefined;
}
}else{
debug('cookieunsigned')
}
}
}
//back-compatreadfromcookieParser()signedCookiesdata
if(!val&&req.signedCookies){
val=req.signedCookies[name];
if(val){
deprecate('cookieshouldbeavailableinreq.headers.cookie');
}
}
//back-compatreadfromcookieParser()cookiesdata
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('cookieshouldbeavailableinreq.headers.cookie');
}
if(val===false){
debug('cookiesignatureinvalid');
val=undefined;
}
}else{
debug('cookieunsigned')
}
}
}
returnval;
}
getcookie方法用于从请求中获取sessionID进行解密,作为秘钥。
//setcookie(res,name,req.sessionID,secrets[0],cookie.data);
//方法作用:为HTTP响应设置cookie,设置的cookie是把req.sessionID进行加密过后的cookie,其中name用于保存到客户端的sessionID的cookie的名称
functionsetcookie(res,name,val,secret,options){
varsigned='s:'+signature.sign(val,secret);
//对要发送的cookie进行加密,密钥为secret
vardata=cookie.serialize(name,signed,options);
//其中options中可能有decode函数,返回序列化的cookie
debug('set-cookie%s',data);
varprev=res.getHeader('set-cookie')||[];
//获取set-cookie头,默认是一个空数组
varheader=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||newMemoryStore
//notifyuserthatthisstoreisnot
//meantforaproductionenvironment
//如果在生产环境下,同时store也就是用户传入的store(默认为MemoryStore)是MemoryStore那么给出警告
if('production'==env&&storeinstanceofMemoryStore){
console.warn(warning);
}
//generatesthenewsession
//为用于指定的store添加一个方法generate,同时为这个方法传入req对象,在这个generate方法中为req指定了sessionID,session,session.cookie
//如果用户传入的secure为auto,
store.generate=function(req){
req.sessionID=generateId(req);
req.session=newSession(req);
req.session.cookie=newCookie(cookieOptions);
//用户指定的secure参数如果是auto,那么修改req.session.cookie的secure参数,并通过issecure来判断
if(cookieOptions.secure==='auto'){
req.session.cookie.secure=issecure(req,trustProxy);
}
};
//查看store是否实现了touch方法
varstoreImplementsTouch=typeofstore.touch==='function';
//为store注册disconnect事件,在该事件中吧storeReady设置为false
store.on('disconnect',function(){storeReady=false;});
//为stroe注册connect事件,把storeReady设置为true
store.on('connect',function(){storeReady=true;});
//exposestore
req.sessionStore=store;
我们知道这个store是用于保存session的地方,默认是一个MemoryStore,但是在生产环境下不建议使用MemoryStore,同时store有很多自定义的方法,如这里就为他添加了generate,connect,disconnect,当然也包含destroy方法。如果你对store感兴趣,可以看看下面这个通用的store具有的所有的方法:
'usestrict';
varEventEmitter=require('events').EventEmitter
,Session=require('./session')
,Cookie=require('./cookie')
varStore=module.exports=functionStore(options){};
//这个Store实例是一个EventEmitter实例,也就是说Store实例最后还是一个EventEmitter实例对象
Store.prototype.__proto__=EventEmitter.prototype;
//每一个store有一个默认的regenerate方法用于产生session
Store.prototype.regenerate=function(req,fn){
varself=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){
varself=this;
//最后调用的是Store的get方法
this.get(sid,function(err,sess){
if(err)returnfn(err);
if(!sess)returnfn();
//如果sess为空那么调用fn()方法
varreq={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){
varexpires=sess.cookie.expires
,orig=sess.cookie.originalMaxAge;
//创建session时候获取其中的cookie域下面的expires,originalMaxAge参数
sess.cookie=newCookie(sess.cookie);
//更新session.cookie为一个Cookie实例而不再是一个{}对象了
if('string'==typeofexpires)sess.cookie.expires=newDate(expires);
sess.cookie.originalMaxAge=orig;
//为新构建的cookie添加originalMaxAge属性
req.session=newSession(req,sess);
//创建一个session实例,其中传入的第一个参数是req,第二个参数是sess也就是我们刚才创建的那个Cookie实例,签名为sess={cookie:cookie对象}
returnreq.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'){
thrownewTypeError('unsetoptionmustbe"destroy"or"keep"');
}
//TODO:switchto"destroy"onnextmajor
varunsetDestroy=options.unset==='destroy';
//determineifsessionshouldbedestroyed
//sessionID还存在,但是req.session已经被销毁了
functionshouldDestroy(req){
//varunsetDestroy=options.unset==='destroy';
returnreq.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中需要设置”信任代理“。
varapp=express()
app.set('trustproxy',1)//trustfirstproxy
app.use(session({
secret:'keyboardcat',
resave:false,
saveUninitialized:true,
cookie:{secure:true}
}))
如果在生产环境下需要使用安全的cookit,同时在测试环境也要能够使用。那么可以使用express中的NODE_ENV参数
varapp=express()
varsess={
secret:'keyboardcat',
cookie:{}
}
if(app.get('env')==='production'){
app.set('trustproxy',1)//trustfirstproxy
sess.cookie.secure=true//servesecurecookies
}
app.use(session(sess))
cookie的secure属性可以设置为auto,那么会按照请求的方式来判断,如果是安全的就是secure。但是如果网站同时支持HTTP和HTTPS,这时候通过HTTPS设置的cookie
对于HTTP是不可见的。这在express的”trustproxy“(简化开发和生产环境)正确设置的情况下特别有用。默认下:cookie.maxAge为null
这意味着,浏览器关闭了这个cookie也就过期了。
req.session:
//Usethesessionmiddleware
app.use(session({secret:'keyboardcat',cookie:{maxAge:60000}}))
//Accessthesessionasreq.session
app.get('/',function(req,res,next){
varsess=req.session//用这个属性获取session中保存的数据,而且返回的JSON数据
if(sess.views){
sess.views++
res.setHeader('Content-Type','text/html')
res.write('views:'+sess.views+'
')
res.write('expiresin:'+(sess.cookie.maxAge/1000)+'s
')
res.end()
}else{
sess.views=1
res.end('welcometothesessiondemo.refresh!')
}
})
其中req.session是一个session对象,格式如下:
session:
//req.session域下面保存的是一个Session实例,其中有cookie表示是一个对象
Session{
//这里是req.session.cookie是一个Cookie实例
cookie:
{path:'/',
_expires:FriMay06201615:44:48GMT+0800(中国标准时间),
originalMaxAge:2591999960,
httpOnly:true},
flash:{error:[Object]
}
}
Session.regenerate():
产生一个session,调用这个方法那么一个新的SID和Session实例就会被创建,同时放置在req.session中。但是第一步是销毁指定的session
Store.prototype.regenerate=function(req,fn){
varself=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=newSession(req);
req.session.cookie=newCookie(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){
//cannotaccesssessionhere
})
session.reload():
重新装载session中的数据
req.session.reload(function(err){
//sessionupdated
})
session.save():
把session中的数据重新保存到store中,用内存的内容去替换掉store中的内容。这个方法在HTTP的响应后自动被调用。如果session中的数据被改变了(这个行为可以通过中间件的很多的配置来改变),正因为如此这个方法一般不用显示调用。但是在长连接的websocket中这个方法一般需要手动调用
req.session.save(function(err){
//sessionsaved
})
session.touch():
更新maxAge属性,一般不需要手动调用,因为session的中间件已经替你调用了。我们看看Session是如何实现这个方法
functionSession(req,data){
Object.defineProperty(this,'req',{value:req});
Object.defineProperty(this,'id',{value:req.sessionID});
if(typeofdata==='object'&&data!==null){
//mergedataintothis,ignoringprototypeproperties
for(varpropindata){
if(!(propinthis)){
this[prop]=data[prop]
}
}
}
}
//重置".cookie.maxAge"防止在session仍然存活的时候cookie已经过期了
defineMethod(Session.prototype,'touch',functiontouch(){
returnthis.resetMaxAge();
});
//resetMaxAge方法,用于为cookie的maxAge指定为cookie的originalMaxAge
defineMethod(Session.prototype,'resetMaxAge',functionresetMaxAge(){
this.cookie.maxAge=this.cookie.originalMaxAge;
returnthis;
});
也就是把session的maxAge设置为构造Session对象的时候的初始值。
req.session.id:
唯一的,而且不会被改变。我们看看Session的构造函数就明白了:
functionSession(req,data){
Object.defineProperty(this,'req',{value:req});
Object.defineProperty(this,'id',{value:req.sessionID});
if(typeofdata==='object'&&data!==null){
//mergedataintothis,ignoringprototypeproperties
for(varpropindata){
if(!(propinthis)){
this[prop]=data[prop]
}
}
}
}
其中defineProperty方法如下:
//重写了Object对象的defineProperty,其中defineProperty用于为这个对象指定一个函数,其中第二个参数是函数的名称,第三个是函数本身
functiondefineMethod(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来完成
varhour=3600000 req.session.cookie.expires=newDate(Date.now()+hour) req.session.cookie.maxAge=hour//和上面的expires等价
当maxAge设置为60000,也就是一分钟,这时候如果已经过去了30s,那么maxAge就会返回30000(不过要等到当前请求结束)。如果这时候我们调用req.session.touch(),那么req.session.maxAge就成了初始值了60000了
req.sessionID:
只读的属性。每一个sessionstore必须是一个EventEmitter对象,同时要实现特定的方法。我们看看MemoryStore把:
functionMemoryStore(){
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)。sessionstore用这个方法去删除那些空闲的session。同时这个方法也会通知sessionstore指定的session是活动态的。MemoryStore实现了这个方法:
//通过指定的sessionId获取当前的session对象,然后把这个对象的cookie更新为新的session对应的cookie,同时sessions中的当前session也进行更新(包括过期时间等)
MemoryStore.prototype.touch=functiontouch(sessionId,session,callback){
varcurrentSession=getSession.call(this,sessionId)
if(currentSession){
//updateexpiration
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,28Apr201610:47:13GMT;HttpOnly
//当有name的时候resopnse中:set-cookie:qinliang=s%3ABDOjujVhV0DH9Atax_gl4DgZ4-1RGvjQ.OeUddoRalzB4iSmUHcE8oMziad4Ig7jUT1REzGcYcdg;Path=/;Expires=Thu,28Apr201610:48:26GMT;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:newMongoStore({
db:settings.db,
host:settings.host,
port:settings.port
})
})
从源码的角度来分析配置项:
(1)这里面的secret到底有什么用呢?
我们看看这个express-session到底是如何做的?
functionunsigncookie(val,secrets){
for(vari=0;i
这里是通过cookie-signature进行的解密操作
//varcookieId=req.sessionID=getcookie(req,name,secrets);
functiongetcookie(req,name,secrets){
varheader=req.headers.cookie;
varraw;
varval;
//readfromcookieheader
if(header){
varcookies=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('cookiesignatureinvalid');
val=undefined;
}
}else{
debug('cookieunsigned')
}
}
}
//back-compatreadfromcookieParser()signedCookiesdata
//如果从req.headers.cookie中没有读取到sessionID的数据,那么就去cookieparser的req.signedCookies中读取
if(!val&&req.signedCookies){
val=req.signedCookies[name];
if(val){
deprecate('cookieshouldbeavailableinreq.headers.cookie');
}
}
//back-compatreadfromcookieParser()cookiesdata
//如果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('cookieshouldbeavailableinreq.headers.cookie');
}
if(val===false){
debug('cookiesignatureinvalid');
val=undefined;
}
}else{
debug('cookieunsigned')
}
}
}
returnval;
}
通过这里我们很容易看到对于sessionID的获取就是通过上面的secret进行签名的,如果获取到的sessionID已经被修改过,那么表示这个session已经无效了。首先是从req.headers.cookie中获取,然后从req.signedCookies中获取,最后从req.cookies中进行获取!
(2)cookie字段有什么用的?
varSession=require('./session/session')
,MemoryStore=require('./session/memory')
,Cookie=require('./session/cookie')
,Store=require('./session/store')
varcookieOptions=options.cookie||{};
functiongenerateSessionId(sess){
returnuid(24);
}
//generatesthenewsession
store.generate=function(req){
req.sessionID=generateId(req);//产生一个sessionID
req.session=newSession(req);//产生一个Session
req.session.cookie=newCookie(cookieOptions);//在req.session对象的cookie域下面保存的是一个Cookie对象
if(cookieOptions.secure==='auto'){
req.session.cookie.secure=issecure(req,trustProxy);
}
};
我们看看cookie字段在哪里被处理了:
varCookie=module.exports=functionCookie(options){
this.path='/';
this.maxAge=null;
this.httpOnly=true;
//最终的this就是这个新创建的Cookie具有这些默认的属性,同时还具有用户自己传入的options参数,如用户传入的varcookieOptions=options.cookie||{};
//也就是用户传入的options.cookie属性
if(options)merge(this,options);
/*这个utils.merge的源码只有一句话:
exports=module.exports=function(a,b){
if(a&&b){
for(varkeyinb){
a[key]=b[key];
}
}
returna;
};*/
this.originalMaxAge=undefined==this.originalMaxAge
?this.maxAge
:this.originalMaxAge;
//默认的originalMaxAge就是this.maxAge也就是null,如果指定了originalMaxAge那么就是用户指定的值
};
也就是说我们在session中传入的cookie参数也成为新创建的cookie的一个属性了,而且这个这个新创建的cookie被保存到req.session.cookie下。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。