HTTP是无状态协议。当浏览器中加载页面,而后转到同一网站的另外一页面时,服务器和浏览器都没有任何内在的方法能够认识到,这是同一浏览器访问同一网站。换一种说法,Web工做的方式就是在每一个HTTP请求中都要包含全部必要的信息,服务器才能知足这个请求。前端
因此须要用某种办法在HTTP上创建状态,因而便有了cookie和会话。git
cookie的想法很简单:服务器发送一点信息,浏览器在一段可配置的时期内保存它。发送哪些信息确实是由服务器来决定:一般只是一个惟一ID号,标识特定浏览器,从而维持一个有状态的假象。数据库
cookie对用户来讲不是加密的;express
服务器向客户端发送的全部cookie都能被客户端查看。npm
用户对cookie有绝对的控制权,能够删除或禁用cookiejson
通常的cookie能够被篡改bootstrap
无论浏览器何时发起一个跟cookie关联的请求,盲目地相信cookie中的内容,都有可能会受到攻击。要确保cookie不被篡改,使用签名cookie。浏览器
用签名cookie会有帮助(不论是用户修改的仍是恶意JavaScript修改的,这些篡改都会在签名cookie中留下明显的痕迹),而且还能够设定选项指明cookie只能由服务器修改。这些cookie的用途会受限,但它们确定更安全。**安全
若是能够选择,会话要优于cookie服务器
大多数状况下,能够用会话维持状态; 而且会话更容易,不用担忧会滥用用户的存储,并且也更安全。
当服务器但愿客户端保存一个cookie时,它会发送一个响应头Set-Cookie
,其中包含名称/值对。当客户端向服务器发送含有cookie的请求时,它会发送多个请求头Cookie,其中包含这些cookie的值。**
推荐用一个随机密码生成器来生成cookie秘钥。**
外化第三方凭证是一种常见的作法,好比cookie秘钥、数据库密码和API令牌(Twitter、Facebook等)。这不只易于维护(容易找到和更新凭证),还可让版本控制系统忽略这些凭证文件。这对放在GitHub或其余开源源码控制库上的开源代码库尤为重要。**
所以能够准备将凭证外化在一个JavaScript文件中(用JSON或XML也行,但我以为JavaScript最容易)。
建立文件credentials.js:
module.exports = { cookieSecret: '把你的cookie秘钥放在这里', };
var credentials = require('./credentials.js');
在程序中开始设置和访问cookie以前,须要先引入中间件cookie-parser
。首先npm install --save cookie-parser
,而后
app.use(require('cookie-parser')(credentials.cookieSecret));
res.cookie('monster', 'nom nom'); res.cookie('signed_monster', 'nom nom', { signed: true });
签名cookie的优先级高于未签名cookie。若是将签名cookie命名为signed_monster
,那就不能用这个名字再命名未签名cookie(它返回时会变成undefined)。
要获取客户端发送过来的cookie的值(若是有的话),只需访问请求对象的cookie或signedCookie
属性:
var monster = req.cookies.monster; var signedMonster = req.signedCookies.monster;
任何字符串均可以做为cookie的名称。
要删除cookie,用res.clearCookie
:
res.clearCookie('monster');
domain
控制跟cookie关联的域名。这样能够将cookie分配给特定的子域名。注意,不能给cookie设置跟服务器所用域名不一样的域名,由于那样它什么也不会作。
path
控制应用这个cookie的路径。注意,路径会隐含地通配其后的路径。若是用的路径是/ (默认值),它会应用到网站的全部页面上。若是用的路径是/foo,它会应用到/foo、/foo/bar等路径上。
maxAge
指定客户端应该保存cookie多长时间,单位是毫秒。若是你省略了这一选项,浏览器关闭时cookie就会被删掉。(也能够用expiration指定cookie过时的日期,但语法很麻烦。建议用maxAge。)
secure
指定该cookie只经过安全(HTTPS)链接发送。
httpOnly
将这个选项设为true代表这个cookie只能由服务器修改。也就是说客户端JavaScript不能修改它。这有助于防范XSS攻击。
signed
设为true会对这个cookie签名,这样就须要用res.signedCookies
而不是res.cookies
访问它。被篡改的签名cookie会被服务器拒绝,而且cookie值会重置为它的原始值。
HTML5为会话提供了另外一种选择,那就是本地存储,但如今尚未使人叹服的理由去采用这种技术而放弃通过验证有效的cookie。
然而出于开发和测试的须要,有它就足够了。
首先安装express-session(npm install --save express-sessio
n)
而后,在链入cookie-parser以后链入express-session:
app.use(require('cookie-parser')(credentials.cookieSecret)); app.use(require('express-session')());
中间件express-session接受带有以下选项的配置对象:
key
存放惟一会话标识的cookie名称。默认为connect.sid
。
store
会话存储的实例。默认为一个MemoryStore的实例,能够知足咱们当前的要求。
cookie
会话cookie的cookie设置 (path
、domain
、secure
等)。适用于常规的cookie默认值。
req.session.userName = 'Anonymous'; var colorScheme = req.session.colorScheme || 'dark';
delete
操做符:req.session.userName = null; // 这会将'userName'设为null;但不会移除它 delete req.session.colorScheme; // 这会移除'colorScheme'
//使用了bootstrap {{#if flash}} <div class="alert alert-dismissible alert-{{flash.type}}"> <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button> <strong>{{flash.intro}}</strong> {{{flash.message}}} </div> {{/if}}
app.use(function(req, res, next){ // 若是有即显消息,把它传到上下文中,而后清除它 res.locals.flash = req.session.flash; delete req.session.flash; next(); });
接下来咱们看一下如何使用即显消息。假设咱们的用户订阅了简报,而且咱们想在用户订阅后把他们重定向到简报归档页面去。咱们的表单处理器多是这样的:
app.post('/newsletter', function(req, res){ var name = req.body.name || '', email = req.body.email || ''; // 输入验证 if(!email.match(VALID_EMAIL_REGEX)) { if(req.xhr) return res.json({ error: 'Invalid name email address.' }); req.session.flash = { type: 'danger', intro: 'Validation error!', message: 'The email address you entered was not valid.', }; return res.redirect(303, '/newsletter/archive'); } new NewsletterSignup({ name: name, email: email }).save(function(err){ if(err) { if(req.xhr) return res.json({ error: 'Database error.' }); req.session.flash = { type: 'danger', intro: 'Database error!', message: 'There was a database error; please try again later.', } return res.redirect(303, '/newsletter/archive'); } if(req.xhr) return res.json({ success: true }); req.session.flash = { type: 'success', intro: 'Thank you!', message: 'You have now been signed up for the newsletter.', }; return res.redirect(303, '/newsletter/archive'); }); });
看如何用同一个处理器处理AJAX提交(由于咱们检查了req.xhr),而且区分开了输入验证错误和数据库错误。记住,即使在前端作了输入验证,在后台也应该再作一次。
即显消息是网站中一种很棒的机制,即使在某些特定区域其余方法更合适一些(好比,即显消息在多表单“向导”或购物车结帐流程中就不太合适)。
由于在中间件里把即显消息从会话中传给了res.locals.flash
,因此必须执行重定向以便显示即显消息。若是不想经过重定向显示即显消息,直接设定res.locals.flash
,而不是req.session.flash
。