本章主要讲什么(一句话)?
本章主要讲解:重构Node后台加入Session支持及前台跨域访问配置
一、前言
上一章主要对项目的路由功能进行完善:登录路由,注册路由,发表文章路由,获取用户信息路由等,本章会继续对于本项目的Node部分功能进行完善。本将也将会是最后一章关于Node,Mongose相关技术及代码的讲解,下一章起,将正式启动angular2的前端部分,敬请期待 :)
二、本章技术关健词
Node、MongoDB、Angular2、Mongoose、Route、Session、跨域
三、本章涉及核心技术点
四、内容
4.1、Session
4.1.1、为什么需要Session?
cookie虽然很方便,但是使用cookie有一个很大的弊端,cookie中的所有数据在客户端就可以被修改,数据非常容易被伪造,那么一些重要的数据就不能存放在cookie中了,而且如果cookie中数据字段太多会影响传输效率。为了解决这些问题,就产生了session,session中的数据是保留在服务器端的。HTTP协议(http://www.w3.org/Protocols/)是“一次性单向”协议。服务端不能主动连接客户端,只能被动等待并答复客户端请求。客户端连接服务端,发出一个HTTP Request,服务端处理请求,并且返回一个HTTP Response给客户端,本次HTTP Request-Response Cycle结束。 我们看到,HTTP协议本身并不能支持服务端保存客户端的状态信息。于是,Web Server中引入了session的概念,用来保存客户端的状态信息。
4.1.2、什么是Session?
Session:在计算机中,尤其是在网络应用中,称为“会话”。Session直接翻译成中文比较困难,一般都译成时域。在计算机专业术语中,Session是指一个终端用户与交互系统进行通信的时间间隔,通常指从注册进入系统到注销退出系统之间所经过的时间。具体到Web中的Session指的就是用户在浏览某个网站时,从进入网站到浏览器关闭所经过的这段时间,也就是用户浏览这个网站所花费的时间。因此从上述的定义中我们可以看到,Session实际上是一个特定的时间概念。
4.1.3、Session的工作原理
一个session就是一系列某用户和服务器间的通讯。服务器有能力分辨出不同的用户。一个session的建立是从一个用户向服务器发第一个请求开始,而以用户显式结束或session超时为结束。
其工作原理是这样的:
1.当一个用户向服务器发送第一个请求时,服务器为其建立一个session,并为此session创建一个标识号;
2.这个用户随后的所有请求都应包括这个标识号。服务器会校对这个标识号以判断请求属于哪个session。这种机制不使用IP作为标识,是因为很多机器是通过代理服务器方式上网,没法区分每一台机器。
形象比喻:
这里用一个形象的比喻来解释session的工作方式。假设Web Server是一个商场的存包处,HTTP Request是一个顾客,第一次来到存包处,管理员把顾客的物品存放在某一个柜子里面(这个柜子就相当于Session),然后把一个号码牌交给这个顾 客,作为取包凭证(这个号码牌就是Session ID)。顾客(HTTP Request)下一次来的时候,就要把号码牌(Session ID)交给存包处(Web Server)的管理员。管理员根据号码牌(Session ID)找到相应的柜子(Session),根据顾客(HTTP Request)的请求,Web Server可以取出、更换、添加柜子(Session)中的物品,Web Server也可以让顾客(HTTP Request)的号码牌和号码牌对应的柜子(Session)失效。顾客(HTTP Request)的忘性很大,管理员在顾客回去的时候(HTTP Response)都要重新提醒顾客记住自己的号码牌(Session ID)。这样,顾客(HTTP Request)下次来的时候,就又带着号码牌回来了。
4.1.4、express中的Session
express中操作session要用到express-session
(https://github.com/expressjs/session )这个模块,主要的方法就是session(options),其中options中包含可选参数,主要有:
Øname:设置cookie中,保存session的字段名称,默认为connect.sid。
Østore:
session的存储方式,默认存放在内存中,也可以使用redis,mongodb等。express生态中都有相应模块的支持。
Øsecret:通过设置的secret字符串,来计算hash值并放在cookie中,使产生的signedCookie防篡改。
Øcookie:设置存放session id的cookie的相关选项,默认为
(default: { path: '/', httpOnly: true,secure: false, maxAge: null })
genid:产生一个新的session_id时,所使用的函数,默认使用uid2这个npm包。
Ørolling:每个请求都重新设置一个cookie,默认为false。
Øresave:即使session没有被修改,也保存session值,默认为true。
express-session默认使用内存来存session,对于开发调试来说很方便
注意:在Express4.x中使用session时要额外安装express-session包:
两种安装方式:
第一种:cnpm install express-session
第二种:package.json包中加入"express-session":"~1.14.1"
到其对应目录下执行:npm install命令
var app = express();
var session= require('express-session');
app.listen(5000);
//按照上面的解释,设置session的可选参数
app.use(session({
secret: 'recommand 128 bytes random string', //建议使用128个字符的随机字符串
cookie: { maxAge: 60 * 1000 }
}));
app.get('/', function (req, res) {
//检查session中的isVisit字段
//如果存在则增加一次,否则为session设置isVisit字段,并初始化为1。
if(req.session.isVisit) {
req.session.isVisit++;
res.send('
第' + req.session.isVisit + '次来此页面
');
} else {
req.session.isVisit = 1;
res.send("欢迎第一次来这里");
console.log(req.session);
}
});
4.1.4、项目中Session的代码
基本上跟上述的配置大同小异,在这里不累述,后面大家可以直接看代码!此处略
4.2、跨域
4.2.1、什么是跨域?
概念:只要协议、域名、端口有任何一个不同,都被当作是不同的域。
URL 说明 是否允许通信
http://www.a.com/a.js
http://www.a.com/b.js 同一域名下 允许
http://www.a.com/lab/a.js
http://www.a.com/script/b.js 同一域名下不同文件夹 允许
http://www.a.com:8000/a.js
http://www.a.com/b.js 同一域名,不同端口 不允许
http://www.a.com/a.js
https://www.a.com/b.js 同一域名,不同协议 不允许
http://www.a.com/a.js
http://70.32.92.74/b.js 域名和域名对应ip 不允许
http://www.a.com/a.js
http://script.a.com/b.js 主域相同,子域不同 不允许
http://www.a.com/a.js
http://a.com/b.js 同一域名,不同二级域名(同上) 不允许(cookie这种情况下也不允许访问)
http://www.cnblogs.com/a.js
http://www.a.com/b.js 不同域名 不允许
4.2.2、为什么浏览器不支持跨域请求
主要的原因还是安全性,防止CSRF攻击。
CSRF是什么?
CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF。
CSRF可以做什么?
你这可以这么理解CSRF攻击:攻击者盗用了你的身份,以你的名义发送恶意请求。CSRF能够做的事情包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账......造成的问题包括:个人隐私泄露以及财产安全。
4.2.3、跨域的几种解决办法
解决跨域访问的办法有N种,最常用的为以下几种:
1> document.domain + iframe (只有在主域相同的时候才能使用该方法)
2> 动态创建script
3> location.hash + iframe
4> window.name + iframe
5> postMessage(HTML5中的XMLHttpRequest Level 2中的API)
6> CORS
7> JSONP
8> web sockets
要想了解以上8种的具体实现,可以参考此篇文章:http://blog.csdn.net/joyhen/article/details/21631833
但就像知道孔乙已的茴香豆的几种写法一样,我认为没有太大的意义,我们更看重的怎么样解决问题。
根据我个人的工作经验,在解决跨域时无外乎两种,一种是客户端想办法,如使用JosnP(这个后面讲Angular2时我会给大家演示),另一种在服务器端想办法,做配置 。相比较而言,第二种会配置更灵活,功能更强大,所以这里我主要给大家演示第二种方式。
4.2.4、Node中前端JS跨域配置代码
好,重点来了,直接上代码:
打开index.js,找到以下代码段:
var crypto = require('crypto'),
User = require('../models/user.js'),
Post = require('../models/post.js');
settings = require('../settings');
module.exports = function(app) {
//此处我们将加入跨域代码配置
}
在上述注释处加入以下代码:
app.all('*', function(req, res, next) {
res.header('Access-Control-Allow-Origin',settings.client);
res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');
res.setHeader("Access-Control-Max-Age", "3600");
res.setHeader("Access-Control-Allow-Credentials", "true"); //是否支持cookie跨域
next();
});
//跨域预检查所有的get请求
app.all('/get/*', function(req, res, next) {
res.header('Access-Control-Allow-Methods', 'GET,OPTIONS');
if (req.method == 'OPTIONS') {
res.send(200);
} else {
console.log(req.method);
next();
}
});
//跨域预检查所有的post请求
app.all('/post/*', function(req, res, next) {
res.header('Access-Control-Allow-Methods', 'POST,OPTIONS');
if (req.method == 'OPTIONS') {
res.send(200);
} else {
console.log(req.method);
next();
}
});
1. app.all('*', function(req, res, next) {。。。}
app.all('/get/*', function(req, res, next) {。。。}
app.all('/post/*', function(req, res, next) {。。。}
注意:第一行 * 代表,所有前端路由请求,必须经过此“过滤器”拦截处理
/get/*,代表所有以get打头的路由请求,必须经过此“过滤器”拦截处理,如:http://localhost:8800/get/user?name=zzz ,这个请求就会被 此“过滤器”拦截
/post/*,代表所有以post打头的路由请求,必须经过此“过滤器”拦截处理,如:http://localhost:8800/post/reg ,这个请求就会被 此“过滤器”拦截
2. res.header('Access-Control-Allow-Origin',settings.client);
这种代码的作用是:只有当目标页面的response中,包含了Access-Control-Allow-Origin这个header,并且它的值里有我们自己的域名时,浏览器才允许我们拿到它页面的数据进行下一步处理,即设置 “同源策略“, 如果它的值设为*,则表示谁都可以用:Access-Control-Allow-Origin: *,但一般我们不会这么干,出于安全性考虑,最好还是设定你允许访问的网站
这名代码相当于设置了:
Access-Control-Allow-Origin : http://localhost:3000
注意settings.client的配置:
settings.js
module.exports = {
cookieSecret: 'myblog',
db: 'blog',
host: 'localhost',
port: 27017,
client:'http://localhost:3000' //用于设置跨域
};
3. res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');
OPTIONS请求头部中会包含以下头部:Origin、Access-Control-Request-Method、Access-Control-Request-Headers,发送这个请求后,服务器可以设置如下头部与浏览器沟通来判断是否允许这个请求。yourHeaderFeild 这个表示你可以自定义与浏览器客户端沟通的信息。
4. res.setHeader("Access-Control-Max-Age", "3600");
Access-Control-Max-Age: 3600 // 表明在3600秒内,不需要再发送预检验请求,可以缓存该结果,即CORS缓存配置
5. res.setHeader("Access-Control-Allow-Credentials", "true"); //是否支持cookie跨域,ture代表支持,注意这个配置很重要,不然后面我们和Angular2 Http请求时,发现不配置此项无法进行服务端的Session操作
6. Access-Control-Allow-Methods : 表明它允许GET、POST、PUT、DELETE的外域请求
注意app.all('/get/*'。。。)与 app.all('/post/*',。。。)里面的内容的区别,这样做的目的,主要还是从更高效和更安全的角度去考虑的
7. 注意,上面各个 ” 过滤器“里的 next() 不可省
五、后述
好了,项目到了这里,关于Node与MongoDB操作部分内容,已经基本全部结束,希望大家能够有所收获,后继我们将继续Angular2部分,因近期工作比较忙,所以更新文章有些慢,大家见谅,大家支持的话,可以给我点赞,评论,转发,您的支持将会是持续下去的动力!
本章代码下载:http://pan.baidu.com/s/1dFf22yt
下章剧透:
《项目实战:基于Angular2+Mongodb+Node技术实现的多用户博客系统教程(11)》
-- Angular2前台框架搭建