express2之database

1.基本名词

在MongoDB中,每个database都会有一个或者多个collection,为了便于理解,可以把collection理解为array。一个collection里可以拥有任意个documents,一个documents很像js里面的对象,同一个collection里面的documents不必存储具有同样数据结构的数据。注意在技术上,documents并不是作为JSON存储的,documents看起来很像json,但是实际上它是binary JSON,俗称BSON。Mongo会给每个document增加一个_id属性,这个_id属性是独一无二的。

2.关于加密库

node不适合做计算密集型的工作任务,而对于密码学来说,往往需要很大的计算量,辛亏在node中可以很方便的调用node的扩展,所以有个bcrypt的库,其使用了C代码来处理这部分计算,加快了计算量。

3.POST请求之body-parser

POST请求传过来的参数可以是多种编码格式的,这个可以通过请求头里面的Content-Type字段看出,比如说下面这些:application/x-www-form-urlencoded(常见于form注册),application/json常见于ajax发送json数据的时候,除了上面两种Content-Type之外,还存在其他常用的content-type,可以查阅相关资料,这里要说的是,在express中如何在req.body里面使用它们:

1
2
3
4
5
const app = express();
const bodyParser = require('body-parser');

app.use(bodyParser.urlencoded({ extended: false })); // 针对application/x-www-form-urlencoded
app.use(bodyParser.json()); // 针对application/json

4.express之用户鉴权

用户鉴权具有很多种方式,常见的有session-cookie机制,token机制,OAuth机制,下面进行逐一介绍:

4-1.session-cookie机制:基本概念:服务端的session,客户端的cookie,利用cookie和session来完成前后端的认证。因为HTTP请求是无状态的,所以服务器不能识别出用户的身份。采用session-cookie机制的话,就引入了一种判断用户身份的唯一标志session,每当用户进行登录的时候,服务端便会为该用户创建一个session(会话),这个session能够用来唯一标识用户,那么问题来了,session是服务端的,那么下次这个客户端的请求到来时,服务器怎么知道是这个用户呢?答案就是让用户携带和自己session所唯一对应的cookie进来,这个cookie在用户进行登录成功的时候服务端便设置好了,以后的客户端的每一次请求便会把这个cookie给携带进来,而服务端在每一次请求到来时都会去查一下该客户在本地有没有对应session,如果有的话,那么就说明验证成功了。

session-cookie机制主要分为下面几个步骤:

①.服务器在用户成功登录后在服务器端创建session->持久化保存session->给这个session生成一个唯一对应的sessionId

②.sessionId签名,根据设置的secret对sid进行加密处理,下次收到sid时接着会根据secret进行解密处理。(注意签名过程非必须)-> 在该请求的响应头中种下这个sid。

③.浏览器在收到响应后,将sid保存到本地cookie中,使得浏览器接下来的请求能够将sid存在cookie中发送给服务端。

④.服务端在需要登录的接口上面验证客户端时候携带了sid,并且查找这个sid是否有在服务端中维护,如果有的话,那么就说明鉴权通过。

4-2:在Express中如何使用passport插件,首先要搞清楚,passport不负责实现鉴权逻辑,它只是一层包装,鉴权逻辑在包装里面,需要开发人员自行编写。使用passport进行用户鉴权的话,开发人员主要完成下面三部分逻辑处理:

①.在Express中引入中间件。

②.告诉passport如何去序列化和反序列化用户,这部分将用户的session映射成了实际的user object。

③.告诉passport怎么进行用户授权,既判断一个用户是否在系统中。 这个时候passport会和Mongo进行接触。

第一步:引入相关中间件,这个步骤依赖body-parser,cookie-parser,express-session,passport,passport-local。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const express = require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const cookieParser = require('cookieParser');
const session = require('express-session');
const passport = require('passport');

const authSetting = require('./authSetting');

const app = express();

mongoose.connect('mongodb://localhost:27017/test', { userNewUrlParser: true });

authSetting(); // 一个配置passport的文件

app.use(passport.initialize());
app.use(passport.session());
app.use(bodyParser.urlencoded({ extended: false })); // content-type 为 x-www-form-urlencoded
app.use(bodyParser.json()); // content-type 为 application/json
app.use(cookieParser());
app.use(session({
secret: 'YOU_SESSION_SECRET',
resave: true,
saveUninitialized: true
}));

第二步:告诉passport怎么序列化用户和反序列化用户。思考一下在express MongoDB技术栈里面如何将用户传过来的sid对应到user object上面,我们知道在MongoDB里面每一个文档都具有一个唯一的_id字段,利用这个字段来唯一标识用户是个可行的办法,并且查询的时候利用_id来查,效率也会比普通字段属性会更好。下面是实例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// authSetting.js
const passport = require('passport');
const User = require('./models/user');

module.exports = function() {
// 序列化用户,将用户转化为id
passport.serializeUser(function(user, done) {
done(null, user._id);
});
// 解序列化用户,将用户id转化为user object
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
};

第三步:如何进行用户授权,在这一步中需要用上策略,这本例中使用本地策略。使用本地策略需要引入一个npm包-passport-local,如下所示:

1
2
// 在原有authSetting.js中加入
const LocalStrategy = require('passport-local').Strategy; // 引入本地passport-策略包

接下来就是制定本地策略,如下所示:1.客户端在进行登录的时候,服务端获取到用户名(或其他id);2.服务端根据这个用户名查找mongodb数据库中是否存在该用户,如果没有的话,可以返回这个用户未注册;3.如果存在这个用户的话,那么继续比对密码,如果密码正确的话,那么返回user object,如果密码比对错误的话,那么返回密码错误。示例代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// authSetting.js
passport.use('login', new LocalStrategy(function(username, password, done) {
User.findOne({username}, function(err, user) {
// 查询数据库的时候报错
if (err) {
return done(err);
}
// 不存在这个用户
if (!user) {
return done(null, false, { message: 'No User Has That Username' });
}
user.checkPassword(password, function(err, isMatch) {
if (err) { // 取密码出错
return done(err);
}
if (isMatch) { // 有这个用户,则返回这个用户
return done(null, user);
} else { // 密码错误
return done(null, false, { message: 'Invalid password.' });
}
});
});
}));

4-2.token机制:和session-cookie很像。大致流程如下所示:

①.客户进行登录

②.如果成功登录,服务端给这个用户生成一个唯一对应的token,并且将这个token返回给客户端

③.客户端将这个token进行存储

④.客户端接将token放在请求头里面发送给服务端

⑤.服务端在需要登录的页面中进行token验证,如果请求通过的话则放行。

和session-cookie的区别:

①.session-cookie需要经过一个中间层sid,而token不需要,就是直接代表了用户,并且在用户端和服务端之间传输。

②.不需要cookie进行配合,

示例代码:

1
2