项目实战(连载):基于Angular2+Mongodb+Node技术实现的多用户博客系统教程(10)

本章主要讲什么(一句话)?

本章主要讲解:重构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前台框架搭建

推荐阅读更多精彩内容