Node.jsのexpress-session設定項目の詳細説明

黄舟
リリース: 2017-06-01 09:32:42
オリジナル
1252 人が閲覧しました

この記事では主にnode.jsのexpress-session設定項目の詳細な説明を紹介していますが、編集者が非常に良い内容だと思ったので、参考として共有させていただきます。エディターをフォローして見てみましょう

公式アドレス: Read

機能: 指定されたパラメータでセッションミドルウェアを作成します。セッションデータはcookieに保存されません。セッション データはサーバー側にのみ保存されます

警告: デフォルトのサーバー側セッション ストレージである MemoryStore は、ほとんどの場合、メモリ リークが発生するため、テストと開発に使用されます。環境

受け入れられるパラメータ:

cookie:つまり、セッション 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:新しいセッションIDを生成します。戻り値がstringtypeである関数functionがsessionIDとして使用されます。この関数の最初のパラメータはreqなので、パラメータが必要な場合に適しています。 in req to generated sessionID

デフォルトの機能は、uid-safe ライブラリを使用して ID 値を生成します (Cookie または URL に使用できるアルゴリズム的に安全な UID を生成します。rand-token および uid2 と比較すると、後者は原因が発生します) % の使用により UID が歪められ、UID が不必要に切り捨てられる可能性があります。私たちの uid-safe は Base64 アルゴリズムを使用しており、その関数 uid(byteLength, callback) の最初のパラメータはビットです。 stringlength の代わりに length)

ソース コード スニペット:

function generateSessionId(sess) { 
 return uid(24); 
} 
 var generateId = options.genid || generateSessionId;

//如果用户没有传入genid参数那么就是默认使用generateSessionId函数来完成
ログイン後にコピー

name:

応答の sessionID Cookie の名前。この名前を通じて読み取ることもできます。デフォルトは connect.sid です。マシン上の同じホスト名とポートで複数のアプリが実行されている場合、セッション Cookie をカットする必要があるため、最善の方法は、name

name = options.name || options.key || 'connect.sid'
  //很显然cookie的name默认是connect.sid,而且首先获取到的name而不是key 
r cookieId = req.sessionID = getcookie(req, name, secrets);
ログイン後にコピー

resave:

Force session Saving toセッションストア。リクエスト中にセッションが変更されていない場合でも。ただし、これは必ずしも必要というわけではありません。クライアントに対して 2 つの並行リクエストがある場合、2 番目のリクエストがセッションを変更しない場合でも、1 つのリクエストによるセッションの変更は別のリクエストによって上書きされる可能性があります。デフォルトは true ですが、デフォルト値は廃止されたため、デフォルトは将来変更される可能性があります。したがって、ニーズを慎重に調査し、ニーズに最も適したものを選択してください。ほとんどの場合、false が必要です。ストアが再保存を設定する必要があるかどうかを知る最良の方法は、ストアが touch メソッド (アイドル状態のセッションを削除) を実装しているかどうかを確認することです。同時に、このメソッドはセッションにも通知します。セッションがアクティブであることを指定するストア)、実装されている場合は、 resave:false を使用できます。 touch メソッドが実装されておらず、ストアが保存されたセッションの有効期限を設定している場合は、 resave: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 がオリジナルのハッシュになります

function isSaved(sess) { 
   return originalId === sess.id && savedHash === hash(sess); 
  }
ログイン後にコピー

rolling:

Forced in セッション ID Cookie はすべての応答で送信されます。有効期限が過去の時刻に設定されている場合、有効期限はデフォルト値に設定されます。ローリングのデフォルトは false です。この値が true に設定されているが、saveUnitialized が false に設定されている場合、Cookie は応答に含まれません (初期化されたセッションはありません)

rollingSessions = options.rolling || false;//默认为false
ログイン後にコピー
環境ローリングが何に使用されるかを見てみましょう:

//这个方法用户判断是否需要在请求头中设置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); 
 }
ログイン後にコピー

明らかに、クライアントのセッション ID がクライアントによって送信されるセッション ID はサーバーの sessionID と一致します。ローリングを true に指定した場合でも、このセッションの cookie はクライアントに送信されます。ただし、req.session を false に設定すると、同時に cookie.expires が発生し、この req.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(&#39;<p>views: &#39; + sess.views + &#39;</p>&#39;) 
  res.write(&#39;<p>expires in: &#39; + (sess.cookie.maxAge / 1000) + &#39;s</p>&#39;) 
  res.end() 
 } else { 
  sess.views = 1 
  res.end(&#39;welcome to the session demo. refresh!&#39;) 
 } 
})
ログイン後にコピー

其中req.session是一个session对象,格式如下:


session:    
 //req.session域下面保存的是一个Session实例,其中有cookie表示是一个对象  
  Session {  
  //这里是req.session.cookie是一个Cookie实例  
   cookie:  
   { path: &#39;/&#39;,  
    _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 === &#39;auto&#39;) { 
  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, &#39;req&#39;, { value: req }); 
 Object.defineProperty(this, &#39;id&#39;, { value: req.sessionID }); 
 if (typeof data === &#39;object&#39; && 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, &#39;touch&#39;, function touch() { 
 return this.resetMaxAge(); 
}); 
//resetMaxAge方法,用于为cookie的maxAge指定为cookie的originalMaxAge 
defineMethod(Session.prototype, &#39;resetMaxAge&#39;, function resetMaxAge() { 
 this.cookie.maxAge = this.cookie.originalMaxAge; 
 return this; 
});
ログイン後にコピー

也就是把session的maxAge设置为构造Session对象的时候的初始值。

req.session.id:

唯一的,而且不会被改变。我们看看Session的构造函数就明白了:


function Session(req, data) { 
 Object.defineProperty(this, &#39;req&#39;, { value: req }); 
 Object.defineProperty(this, &#39;id&#39;, { value: req.sessionID }); 
 if (typeof data === &#39;object&#39; && 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: &#39;/&#39;, 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) === &#39;s:&#39;) { 
    //切割掉前面的字符"s:"! 
    val = unsigncookie(raw.slice(2), secrets); 
    //val表示false意味着客户端传递过来的cookie被篡改了! 
    if (val === false) { 
     debug(&#39;cookie signature invalid&#39;); 
     val = undefined; 
    } 
   } else { 
    debug(&#39;cookie unsigned&#39;) 
   } 
  } 
 } 
 // 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(&#39;cookie should be available in req.headers.cookie&#39;); 
  } 
 } 
 // 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) === &#39;s:&#39;) { 
    val = unsigncookie(raw.slice(2), secrets); 
    if (val) { 
     deprecate(&#39;cookie should be available in req.headers.cookie&#39;); 
    } 
    if (val === false) { 
     debug(&#39;cookie signature invalid&#39;); 
     val = undefined; 
    } 
   } else { 
    debug(&#39;cookie unsigned&#39;) 
   } 
  } 
 } 
 return val; 
}
ログイン後にコピー

通过这里我们很容易看到对于session ID的获取就是通过上面的secret进行签名的,如果获取到的sessionID已经被修改过,那么表示这个session已经无效了。首先是从req.headers.cookie中获取,然后从req.signedCookies中获取,最后从req.cookies中进行获取!

(2)cookie字段有什么用的?


var Session = require(&#39;./session/session&#39;) 
 , MemoryStore = require(&#39;./session/memory&#39;) 
 , Cookie = require(&#39;./session/cookie&#39;) 
 , Store = require(&#39;./session/store&#39;) 
 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 === &#39;auto&#39;) { 
   req.session.cookie.secure = issecure(req, trustProxy); 
  } 
 };
ログイン後にコピー

我们看看cookie字段在哪里被处理了:


var Cookie = module.exports = function Cookie(options) { 
 this.path = &#39;/&#39;; 
 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下。

以上がNode.jsのexpress-session設定項目の詳細説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート