前篇 - 基本认证,用户名密码
后篇 - OAuth2 认证javascript
因为宠物店的业务发展须要,咱们须要一种更加便捷的方式来管理日益增多的宠物和客户。最好的方法就是开发一个APP,我能够用这个APP来添加、更新和删除宠物。同时,业务要给宠物店的会员用户有限查看某些宠物。html
咱们在开发中会用到NodeJs以及基于NodeJs的开发框架,如:Express,Mongoose(用来管理MongoDB的数据),Passport(认证)等工具。项目代码在这里。java
咱们这个项目的结构大概是这样的:node
petshot/ //服务端和客户端(android) server/ //服务端 models/ //实体类 pet.js user.js node_modules/ //npm安装的包,无需手动修改 package.json //project定义和依赖声明 server.js //服务端的一切从这里开始
注意:node_modules这个目录你不须要建立,在执行npm安装命令以后这个目录会自动生成,而且npm命令会自动管理这个目录。android
这里假设你已经跳转到petshop/server/目录下了。以后在Terminal里执行命令:git
npm init
按照提示,依次在Terminal里输入相应的信息。最后npm命令生成package.json文件。文件是这样的:github
{ "name": "petshop-server", "version": "0.1.0", "description": "petshop nodejs server", "main": "server.js", "dependencies": { } }
内容不少,这里是一部分。web
为何NodeJs能让那么多的开发者青睐有加,npm命令绝对也是一个数得上的缘由了。下面就体会一下,npm命令。mongodb
首先,使用npm命令安装Express:chrome
npm install --save express
npm命令会选择合适的Express版本下载安装在本地。其余的包也是这么安装的。很是简单。
有了Express,Server就已经能够运行起来了。若是你尚未建立server.js,请建立。在server.js中添加以下的代码:
// 引入咱们须要的包express var express = require('express'); // 建立一个express的server var app = express(); // server运行的端口号 var port = process.env.PORT || '3090'; // 使用express的路由器 var router = express.Router(); // 访问http://localhost:3090/api的时候, // 返回一个json router.get('/', function (req, res) { res.json({'message': '欢迎来到宠物商店'}); }); // 给路由设定根路径为/api app.use('/api', router); // 运行server,并监听指定的端口 app.listen(port, function () { console.log('server is running at http://localhost:3090'); });
经过require获得的express,就能够初始化出一个express的server。以后指定路由,并指定路由的相对根路径。在app上调用listen
方法,并指定端口。这样express的server就运行起来了。
在Terminal中输入命令:
node server.js
测试一下咱们的第一个server是否能够运行。在浏览器中输入地址:*http://localhost:3090/api*就能够看到运行结果了。
为了开发的更加方便,仅仅以上介绍的内容是不够的。好比,修改代码以后,使用命令node server.js
来重启server。设置断点,单步调试等。咱们来看看这几个工具:
微软改邪归正依赖的第一个靠谱的工具。正好这个工具很是好的支持了NodeJs的开发。Visual Code还默认继承了Git代码管理工具。这让我很是愿意多安利几句。
而且使用visual code能够很是方便的调试。好比,设置断点、单步调试,step in、step out等均可以。还能够鼠标悬浮查看变量值等。
postman有chrome浏览器版本的应用,这样不管你在什么平台上开发只要安装了Chrome浏览器就能够装一个postman。这个工具是用来检查RESTful API的。直接使用浏览器得出来的Json字符串有的时候没有格式化,或者格式化不充分,很是难阅读。
而且直接使用浏览器无法模拟Http post请求。而Postman很好的解决了以上问题。因此,开发必备神器之一postman,你值得拥有。
业界著名的非关系数据库MongoDB。咱们在petshot的server端使用该库来存储数据。请按照官网说明下载安装(其实就是把编译好的二进制文件放到一个指定目录)。
数据库安装好以后,就须要链接数据库的框架了。这就须要用到mongoose。Mongoose是处理MongoDB的一个ORM框架。
npm install --save mongoose
安装好以后在代码中引入(require)。
var express = require('express'); var mongoose = require('mongoose');
链接到数据库:
// 链接数据库 mongoose.connect('mongodb://localhost:27017/petshot');
MVC的开发模式这里就再也不科普了。凡是MVC模式下开发,就必定会有Model,MVC的M。通常每个model都和数据库中的一个“表”对应,在mongodb里“表”正式名称为“Collection”。咱们这里只使用英文,专有名词不必翻译。而“表”里的每一条“记录”又叫作“Document”。
咱们首先在petshop/server/models/目录下新建一个文件pet.js。以后添加以下代码:
// 1. 引入mongoose var mongoose = require('mongoose'); var Schema = mongoose.Schema; // 2. 定义了Pet的Schema var petSchema = new Schema({ name: {type: String, required: true}, type: {type: String, required: true}, quantity: Number }); // 3. 定义并export了一个Model module.exports = mongoose.Model('pet', petSchema);
相关解释:
首先,须要一个包(package)来解析http发送过来的数据。那么安装之:
npm install --save body-parser
在代码中引入:
var express = require('express'); var mongoose = require('mongoose'); // 稍后处理数据使用 var Pet = require('./models/pet'); // 解析http数据 var bodyParser = require('body-parser');
下面还须要设置body-parser
:
var app = express(); app.use(bodyParser.urlencoded({ extended: true }));
咱们的server终于迎来可使用的一个功能了:给宠物商店添加宠物。
router.get('/', function (req, res) { res.json({'message': '欢迎来到宠物商店'}); }); var petRouter = router.route('/pets'); petRouter.post(function (req, res) { var pet = new Pet(); pet.name = req.body.name; pet.type = req.body.type; pet.quantity = req.body.quantity; pet.save(function (err) { if (err) { res.json({message: 'error', data: err}); return; } res.json({message: 'done', data: pet}); }); });
咱们增长了一个/pets的相对路径来处理POST请求,并在请求中获取信息,把用这个信息来给初始化的pet的model赋值,并调用这个model的save
方法保存数据到数据库。
打开postman,各个设置如图:
这样就能够往数据库添加pet数据了。
如今的代码虽然已经能够添加宠物宝宝了。可是,后面若是咱们还要添加其余功能,好比:查找,更新和删除等的时候,server.js
文件势必会不断增长。这样给之后代码的维护带来困扰。因此咱们要重构代码。
在petshop/server/目录下添加controllers目录。根据MVC的模式开发,把处理业务方面的代码都存放在某个controller里。新建pet.js
文件。这个文件就做为pet的controller。并将宠物的增删改查都放在这个文件中处理:
var Pet = require('../models/pet'); var postPets = function(req, res) { var pet = new Pet(); pet.name = req.body.name; pet.type = req.body.type; pet.quantity = req.body.quantity; pet.save(function (err) { if (err) { res.json({message: 'error', data: err}); return; } res.json({message: 'done', data: pet}); }); }; var getPets = function(req, res) { Pet.find(function (err, pets) { if (err) { res.json({message: 'error', data: err}); return; } res.json({message: 'done', data: pets}); }); }; var getPet = function(req, res) { Pet.findById(req.params.pet_id, function (err, pet) { if (err) { res.json({message: 'error', data: err}); return; } res.json({message: 'done', data: pet}); }); }; var updatePet = function(req, res) { Pet.findById(req.params.pet_id, function(err, pet) { if (err) { res.json({message: 'error', data: err}); return; } pet.quantity = req.params.quantity; pet.save(function (err) { if (err) { res.json({message: 'error', data: err}); return; } res.json({message: 'done', data: pet}); }); }); }; var deletePet = function(req, res) { Pet.findByIdAndRemove(req.params.pet_id, function(err) { if (err) { res.json({message: 'error', data: err}); return; } res.json({message: 'done', data: {}}); }); } module.exports = { postPets: postPets, getPets: getPets, getPet: getPet, updatePet: updatePet, deletePet: deletePet };
原来的server.js也须要重构:
var Pet = require('../models/pet'); var postPets = function(req, res) { var pet = new Pet(); pet.name = req.body.name; pet.type = req.body.type; pet.quantity = req.body.quantity; pet.save(function (err) { if (err) { res.json({message: 'error', data: err}); return; } res.json({message: 'done', data: pet}); }); }; var getPets = function(req, res) { Pet.find(function (err, pets) { if (err) { res.json({message: 'error', data: err}); return; } res.json({message: 'done', data: pets}); }); }; var getPet = function(req, res) { Pet.findById(req.params.pet_id, function (err, pet) { if (err) { res.json({message: 'error', data: err}); return; } res.json({message: 'done', data: pet}); }); }; var updatePet = function(req, res) { Pet.findById(req.params.pet_id, function(err, pet) { if (err) { res.json({message: 'error', data: err}); return; } pet.quantity = req.params.quantity; pet.save(function (err) { if (err) { res.json({message: 'error', data: err}); return; } res.json({message: 'done', data: pet}); }); }); }; var deletePet = function(req, res) { Pet.findByIdAndRemove(req.params.pet_id, function(err) { if (err) { res.json({message: 'error', data: err}); return; } res.json({message: 'done', data: {}}); }); } module.exports = { postPets: postPets, getPets: getPets, getPet: getPet, updatePet: updatePet, deletePet: deletePet };
咱们当让不行让谁均可以添加宠物宝宝了。查看是能够的,添加须要控制。
Passport
就是给基于Express开发的web应用的,专一于认证中间件。也有和body-parser
相相似的使用方法。passport的功能很是丰富,不过咱们先使用最简单的一种认证策略。
安装:
npm install --save passport-http
认证之前首先要有用户数据。
同时还有一个包须要安装:
npm install --save bcrypt-nodejs
这个包是用来给密码hash用的。
全部关于用户的数据都放在MongoDB的user colleciton里,并有user model与之对应。在models目录下新建user.js文件。
var mongoose = require('mongoose'), bcrypt = require('bcrypt-nodejs'); var Schema = mongoose.Schema; var userSchema = new Schema({ username: {type: String, unique: true, required: true}, password: {type: String, required: true} }); // * called before 'save' method. userSchema.pre('save', function (next) { var self = this; if (!self.isModified('password')) { return next(); } bcrypt.genSalt(5, function (err, salt) { if (err) { return next(err); } bcrypt.hash(self.password, salt, null, function (err, hash) { if (err) { return next(err); } self.password = hash; next(); }); }); }); module.exports = mongoose.model('User', userSchema);
使用userSchema.pre('save', function(next){})
给model添加了一个在save
方法调用以前先执行的方法。在这个方法里首先检查用户的密码是否有修改,若是有则使用包bcrypt-nodejs
来hash用户的密码。
有了model,就须要对应的controller来处理。在controllers目录下新建一个user.js
文件做为user controller。注意:实际开发的时候你确定是不会把所有用户的信息都发到客户端的,里面包含了hash的用户密码。
var User = require('../models/user'); var postUsers = function (req, res) { var user = new User({ username: req.body.username, password: req.body.password }); user.save(function (err) { if (err) { res.json({message: 'error', data: err}); return; } res.json({message: 'done', data: user}); }); }; var getUsers = function (req, res) { User.find(function (err, users) { if (err) { res.json({message: 'error', data: err}); return; } res.json({message: 'done', data: users}); }); }; module.exports = { postUsers: postUsers, getUsers: getUsers };
定义user controller的路由:
... var petController = require('./controllers/pet') userController = require('./controllers/user'); ... // path: /users, for users router.route('/users') .post(userController.postUsers) .get(userController.getUsers);
你已经能够在这个路径:*http://localhost:3090/api/users*下POST添加用户,GET获取所有用户。
在开始之前首先确保你已经安装了认证须要的包:
npm install --save passport npm install --save passport-http
以后给在user model里添加一个方法验证password:
userSchema.methods.verifyPassword = function (password, callback) { bcrypt.compare(password, this.password, function (err, match) { if (err) { return callback(err); } callback(null, match); }); };
接下来,在controllers目录下添加auth.js文件。
var passport = require('passport'), BasicStrategy = require('passport-http').BasicStrategy, User = require('../models/user'); passport.use(new BasicStrategy( function(username, password, done) { User.findOne({username: username}, function(err, user) { if (err) { return done(err); } // 用户不存在 if (!user) { return done(null, false); } // 检查用户的密码 user.verifyPassword(passowrd, function(err, match) { // 密码不匹配 if (!match) { return done(null, false); } // 成功 return done(null, user); }); }); } )); module.exports.isAuthenticated = passport.authenticate('basic', {session: false});
咱们使用包passport-http
的BasicStrategy
来处理http的用户认证。首先,咱们经过用户名查找用户。若是用户存在,接着验证用户的密码是否与数据库的数据一致。若是以上两步经过验证则用户认证成功,不然不成功。
最后一句就是告知passport使用BasicStrategy来认证用户。session为false,是告诉passport不存储用户的session。用户每一次的http请求都须要提供用户名和密码。
相应的更新server.js:
// 引入咱们须要的包express var express = require('express'), mongoose = require('mongoose'), bodyParser = require('body-parser'), passport = require('passport'), petController = require('./controllers/pet'), userController = require('./controllers/user'), authController = require('./controllers/auth'); // 建立一个express的server var app = express(); app.use(bodyParser.urlencoded({ extended: true })); // 链接数据库 mongoose.connect('mongodb://localhost:27017/petshot'); ... router.route('/pets') .post(authController.isAuthenticated, petController.postPets) .get(authController.isAuthenticated, petController.getPets); router.route('/pets/:pet_id') .get(authController.isAuthenticated, petController.getPet) .put(authController.isAuthenticated, petController.updatePet) .delete(authController.isAuthenticated, petController.deletePet); // path: /users, for users router.route('/users') .post(userController.postUsers) .get(authController.isAuthenticated, userController.getUsers); ...
若是尚未数据的话,首先使用POST方法添加几个用户。以后GET用户测试一下。若是用户名、密码都对的话就会得到数据了。
使用passport包认证还有一个好处,你能够直接从req获取user数据。如:req.user._id
得到用户的_id。有些数据须要记录更新数据的用户,这样就很是方便了。
下文使用更加安全的oauth2认证。