电子书:http://download.csdn.net/download/qfire/10232839
代码:https://github.com/EthanRBrown/web-development-with-node-and-express
第十三章 持久化
保存文件:
云持久化:AWS
数据库持久化:《MongoDB权威指南》。选免费的MongoDB托管服务MongoLab。
$ subl models/vacation.js
用MongoDB存储会话数据:session-mongoose包
第十四章 路由
信息架构(IA)是指内容的概念性组织。最经典的是Tim Bernes-Lee写的。http://www.w3.org/ Provider/Style/URI.html。
有些建议能帮你实现持久的IA:绝不在URL中暴露技术细节、避免在URL中出现无意义的信息、避免无谓的长URL、单词分隔符要保持一致、绝不要用空格或不可录入的字符、在URL中用小写字母。
子域名:用vhost包
路由路径和正则表达式:
路由参数:
第十五章 REST API和JSON
Meadowlark维护着一个景点数据库,并配以有趣的历史事实。API允许创建移动端应用,让游客可以用他们的手机或平板自我导游。如果设备能感知位置,应用还可以让他们知道自己是否靠近一个有趣的景点。为了让数据库增长,API还支持添加地标和景点。
API规划:
- GET /api/attractions:获取景点。以lat,lng和radius为查询字符串参数,返回一个景点列表。
- GET /api/attraction/:id :根据ID返回一处景点
- POST /api/attraction:以lat, lng, name, description和email为请求体添加新的景点。新添加的景点会进入一个待审批队列。
- PUT /api/attraction/:id :更新一处已有的景点。参数为景点的ID, lat, lng, name, description和email。更新会进入待审批队列。
- DEL /api/attraction:id : 删除景点。参数为景点ID、email和reason。删除会进入待审批队列。
API错误报告:
跨域资源共享:API访问需要跨站,使用cors包
数据存储:models/attraction.js
测试:
用Express提供API:
使用REST插件:
使用子域名:API实质上是不同于网站,所以很多人都会选择用子域将API跟网站其余部分分开。如api.meadowlarktraval.com。
第十六章 静态内容
是不会基于每个请求而去改变的资源。如何处理静态资源对网站的性能有很大影响,特别是网站有很多多媒体内容时。在性能上主要考虑两点:减少请求次数和缩减内容的大小。
推荐用https://wearekiss.com/spritepad创建子画面CSS
推荐使用CDN
服务器和客户端JavaScript中的静态资源
打包和缩小:使用Grunt,但开发模式就不能调试,
第十七章 在Express中实现MVC
模型:models
视图模型:对原模型进一步提取,viewModels/
控制器:负责处理用户交互,并根据用户交互选择恰当的视图来显示。controllers
第十八掌 安全
如果你允许人们登录,或者存储个人身份信息,就要给网站实现某种安全机制。
HTTPS:互联网的本质决定了第三方有可能截取客户端和服务端之间传输的数据包。HTTPS会对那些包进行加密,让攻击者极难访问到所传输的信息。你可以把HTTPS当作确保网站安全的基础。它不提供认证,但为认证奠定了基础。比如说,认证系统可能涉及传输密码:如果密码是未经加密进行传输的,再复杂的认证也不能确保系统的安全。安全的强度取决于整个体系中最弱的一环节,而其中第一环节就是网络协议。
HTTPS协议基于服务器上的公钥证书,有时也叫SSL证书。SSL证书目前的标准格式是X.509。证书背后的思想是有证书颁发机构(CA)发行证书。CA让浏览器厂商能访问授信根证书。在你安装浏览器时,其中就包含这些授信根证书,并靠它们建立起CA和浏览器之间的信任链。要用这个信任链,你的服务器必须使用有CA颁发的证书。
生成自己的证书:
生成私钥和公共证书
$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout meadowlark.pem -out meadowlark.crt
Express启用HTTPS:ssl目录下
var https = require('https'); // 一般在文件顶部
var options = {
key: fs.readFileSync(__dirname + '/ssl/meadowlark.pem');
cert: fs.readFileSync(__dirname + '/ssl/meadowlark.crt');
};
https.createServer(options, app).listen(app.get('port'), function(){
console.log('Express started in ' + app.get('env') +
' mode on port ' + app.get('port') + '.');
});
HTTPS和代理:
跨站请求伪造(CSRF):利用了用户一般都会相信浏览器并且在同一个会话中访问多个网站这样的事实。在CSRF攻击中,恶意站点上的脚本会请求另外一个网站:如果你在另一个网站上登录过,恶意网站可以成功访问那个网站上的安全数据。
要防范CSRF攻击,你必须想办法确保请求合法地来自你的网站,我们的做法是给浏览器传一个唯一的令牌。当浏览器提价表单时,服务器会进行检查,以确保令牌是匹配的。csurf中间件负责令牌的创建和验证;你只需要确保令牌包含在到服务器的请求中。在所有的表单以及AJAX调用都必须提供一个叫_csrf的域,它必须跟生成的令牌相匹配。
$ npm install --save csurf
认证:我能给你的最重要的经验是别试图自己做。如果你的名片上没有“安全专家”这样的头衔,可能还不清楚设计一个安全认证系统需要怎样复杂周密的思考。
一般来说,先认证,然后确定授权。授权可能非常简单、宽泛、或非常细化,指定不同账号类型的读、写、删除和更新权限。
第三方认证:
把用户存在数据库中:models/user.js
var mongoose = require('mongoose');
var userSchema = mongoose.Schema({
authId: String, //用以映射到第三方认证,如facebook:2343423,而Twitter:1214421
name: String,
email: String,
role: String, //客户或员工
created: Date,
});
var User = mongoose.model('User', userSchema);
module.exports = User;
认证与注册和用户体验:“第三方混乱”是要考虑的一个用户体验状况。如果用户在一月份用Facebook注册了你的服务,然后在七月份回来了。
Passport:是为Node/Express做的认证模块,非常健壮,也非常流行。它没有绑定死在任何认证机制上,而是基于可插拔认证策略的思想(如果你不想用第三方认证,它也有本地策略)。
用第三方认证要明白的重要细节是你的应用绝对不会收到密码。完全是有第三方处理的。这是好事:第三方承担了安全处理和密码存储的重担。
然后整个过程要靠重定向完成。一开始你可能会觉得疑惑,你为什么能将本地服务器的URL传给第三方,而且还可以成功认证呢。这之所以能实现,是因为第三方只是告知你的浏览器让它重定向,而你的浏览器处于你的网络中,因此可以重定向到本地地址。
在使用Passport时,你的应用要负责四步。
搭建Passport:使用Facebook认证https://developers.facebook.com/docs,为了测试用http://localhost:3000跟facebook应用关联的。
需要APP ID 和密钥
$ npm install --save passport passport-facebook
var User = require('../models/user.js');
var passport = require('passport');
var FacebookStrategy = require('passport-facebook').Strategy;
//将请求映射到认证用户上,允许你使用任何存储方法。
passort.serializeUser(function(user, done){
done(null, user._id); //MongoDB的_id属性
});
//实际上只在会话里存了一个用户ID,然后当需要时,会从数据库中查找那个ID得到User模型的实例
//只要有活跃的会话,并且用户成功通过认证,req.session.passport.user就会对应上User模型的实例
passort.deserializeUser(function(id, done){
User.findById(id, function(err, user) {
if (err || !user) return done(err, null);
done(null, user);
});
});
module.exports = function(app, options){
//如果没有指定成功和失败的重定向地址
//设定一些合理的默认值
if (!options.successRedirect)
options.successRedirect = '/accout';
if (!options.failureRedirect)
options.failureRedirect = '/login';
return {
init: function(){
var env = app.get('env');
var config = options.providers;
//配置Facebook策略
passport.use(new FacebookStrategy({
clientID: config.facebook[env].appId,
clientSecret: config.facebook[env].appSecret,
callbackURL: '/auth/facebook/callback',
}, function(accessToken, refreshToken, profile, done){
//当认证成功后,profile中有Facebook用户的信息
var authId = 'facebook:' + profile.id; //加了facebook前缀
User.findOne({ authId: authId }, function(err, user){
if (err) return done(err, null);
if (user) return done(null, user);
user = new User({
authId: authId,
name: profile.displayName,
created: Date.now();
role: 'customer',
});
//保存
user.save(function(err){
if (err) return done(err, null);
done(null, user);
});
});
}));
app.use(passport,initialize());
app.use(passport,session());
},
registerRoutes: function() {
//注册Facebook路由
app.get('/auth/facebook', function(req, res, next) {
passport.authenticate('facebook', {
callbackURL: '/auth/facebook/callback?redirect=' + encodeURIComponent(req.query.redirect),
})(req, res, next);
});
app.get('/auth/facebook/callback', passport.authenticate('facebook', {
failureRedirect: options.failureRedirect,
}, function(req, res) {
//只有认证成功才能到这里
res.redirect(303, req.query.redirect || options.successRedirect );
}
));
},
};
};
基于角色的授权: