基于Node的PetShop,RESTful API以及认证
前篇 - 基本认证,用户名密码
后篇 - OAuth2 认证
由于宠物店的业务发展需要,我们需要一种更加便捷的方式来管理日益增多的宠物和客户。最好的方法就是开发一个APP,我可以用这个APP来添加、更新和删除宠物。同时,业务要给宠物店的会员用户有限查看某些宠物。
我们在开发中会用到NodeJs以及基于NodeJs的开发框架,如:Express,Mongoose(用来管理MongoDB的数据),Passport(认证)等工具。项目代码在这里。
开始
我们这个项目的结构大概是这样的:
petshot/ //服务端和客户端(android)
server/ //服务端
models/ //实体类
pet.js
user.js
node_modules/ //npm安装的包,无需手动修改
package.json //project定义和依赖声明
server.js //服务端的一切从这里开始
注意:node_modules这个目录你不需要创建,在执行npm安装命令之后这个目录会自动生成,并且npm命令会自动管理这个目录。
定义项目和依赖的包
这里假设你已经跳转到petshop/server/目录下了。之后在Terminal里执行命令:
npm init
按照提示,依次在Terminal里输入相应的信息。最后npm命令生成package.json文件。文件是这样的:
{
"name": "petshop-server",
"version": "0.1.0",
"description": "petshop nodejs server",
"main": "server.js",
"dependencies": {
}
}
内容很多,这里是一部分。
安装所需要的包
为什么NodeJs能让那么多的开发者青睐有加,npm命令绝对也是一个数得上的原因了。下面就体会一下,npm命令。
首先,使用npm命令安装Express:
npm install --save express
npm命令会选择合适的Express版本下载安装在本地。其他的包也是这么安装的。非常简单。
运行Server
有了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。设置断点,单步调试等。我们来看看这几个工具:
1. Visual Code
微软改邪归正依赖的第一个靠谱的工具。正好这个工具非常好的支持了NodeJs的开发。Visual Code还默认继承了Git代码管理工具。这让我非常愿意多安利几句。
并且使用visual code可以非常方便的调试。比如,设置断点、单步调试,step in、step out等都可以。还可以鼠标悬浮查看变量值等。
2. Postman
postman有chrome浏览器版本的应用,这样无论你在什么平台上开发只要安装了Chrome浏览器就可以装一个postman。这个工具是用来检查RESTful API的。直接使用浏览器得出来的Json字符串有的时候没有格式化,或者格式化不充分,非常难阅读。
并且直接使用浏览器没法模拟Http post请求。而Postman很好的解决了以上问题。所以,开发必备神器之一postman,你值得拥有。
MongoDB
业界著名的非关系数据库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');
创建model
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);
相关解释:
- 引入mongoose库。
- 定义了一个mongoose的Schema,这个Schema和一个mongodb的collection的定义相对应。这个schema有两个字符串和一个数字类型的属性。
- 把mongoose的Model export出去给server端的其他代码使用。哪里使用哪里就require这个model。
准备接收HTTP数据
首先,需要一个包(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
Passport
就是给基于Express开发的web应用的,专注于认证中间件。也有和body-parser
相类似的使用方法。passport的功能非常丰富,不过我们先使用最简单的一种认证策略。
安装:
npm install --save passport-http
认证以前首先要有用户数据。
同时还有一个包需要安装:
npm install --save bcrypt-nodejs
这个包是用来给密码hash用的。
用户model
所有关于用户的数据都放在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用户的密码。
User Controller
有了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认证。
基于Node的PetShop,RESTful API以及认证的更多相关文章
- RESTful Api 身份认证安全性设计
REST是一种软件架构风格.RESTful Api 是基于 HTTP 协议的 Api,是无状态传输.它的核心是将所有的 Api 都理解为一个网络资源.将所有的客户端和服务器的状态转移(动作)封装到 H ...
- 关于RESTFUL API 安全认证方式的一些总结
常用认证方式 在之前的文章REST API 安全设计指南与使用 AngularJS & NodeJS 实现基于 token 的认证应用两篇文章中,[译]web权限验证方法说明中也详细介绍,一般 ...
- RESTful Api 身份认证中的安全性设计探讨
REST 是一种软件架构风格.RESTful Api 是基于 HTTP 协议的 Api,是无状态传输.它的核心是将所有的 Api 都理解为一个网络资源.将所有的客户端和服务器的状态转移(动作)封装到 ...
- 使用 node-odata 轻松创建基于 OData 协议的 RESTful API
前言 OData, 相信身为.NET程序员应该不为陌生, 对于他的实现, 之前也有童鞋进行过介绍(见:这里1,这里2). 微软的WCF Data Service即采用的该协议来进行通信, ASP.NE ...
- 关于 RESTFUL API 安全认证方式的一些总结
常用认证方式 在之前的文章REST API 安全设计指南与使用 AngularJS & NodeJS 实现基于 token 的认证应用两篇文章中,[译]web权限验证方法说明中也详细介绍,一般 ...
- 基于Node的PetShop,oauth2认证RESTful API
前篇 - 基本认证,用户名密码 后篇 - OAuth2 认证 前文使用包passport实现了一个简单的用户名.密码认证.本文改用oauth2来实现更加安全的认证.全部代码在这里. OAUTH2 用户 ...
- Node.js实现RESTful api,express or koa?
文章导读: 一.what's RESTful API 二.Express RESTful API 三.KOA RESTful API 四.express还是koa? 五.参考资料 一.what's R ...
- Node.js:RESTful API
ylbtech-Node.js:RESTful API 1.返回顶部 1. Node.js RESTful API 什么是 REST? REST即表述性状态传递(英文:Representational ...
- 基于Spring Boot的RESTful API实践(一)
1. RESTful简述 REST是一种设计风格,是一组约束条件及原则,而遵循REST风格的架构就称为RESTful架构,资源是RESTful的核心,一个好的RESTful架构,通过URL就能很 ...
随机推荐
- ini,config文件的读取修改
修改ini配置文件 // 声明INI文件的写操作函数 WritePrivateProfileString() [System.Runtime.InteropServices.DllImport(&qu ...
- 怎么查看windows2003中隐藏用户
在命令模式下删除1.你在MS-dos下先输入net user 看有那些用户, 注意第一步看不出隐藏的用户 2.然后在输入net localgroup administrators 或者 net loc ...
- paper 21 :Libsvm的安装和使用
看了很多资料(包括我们实验室群里师兄上传的资料),算是掌握了libsvm的正确安装和使用,把结果告诉大家以方便以后使用. 1. 参考网站: libsvm库下载:http://www.csie.ntu. ...
- springmvc+spring+mybatis分页查询实例版本2.0
先在改成纯利用js进行分页,首先查询出所有记录,初始化通过jquery控制只知显示首页内容,创建页面切换功能的函数,每次显示固定的内容行并把其他内容行隐藏,这样只需要一次提交就可以实现分页,但是仍有缺 ...
- 10.Java设计模式 工厂模式,单例模式
Java 之工厂方法和抽象工厂模式 1. 概念 工厂方法:一抽象产品类派生出多个具体产品类:一抽象工厂类派生出多个具体工厂类:每个具体工厂类只能创建一个具体产品类的实例. 即定义一个创建对象的接口(即 ...
- jquery遍历对象,数组,集合
1.jquery 遍历对象 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTM ...
- JSP注意点
一.JSP页面会编译成一个Servlet类,每个Servlet在容器中只有一个实例:在JSP中声明的变量是成员变量,成员变量只在创建实例时初始化,该变量的值将一直保存,直到实例销毁: 二.输出表达式& ...
- awk,perl,python的命令行参数处理
Python,Perl,Bash命令行参数 Part I 日常经常性的和Perl,Python,Bash打交道,但是又经常性的搞混他们之间,在命令行上的特殊性和index的区别,Python真的是人性 ...
- Linux按键驱动程序设计详解---从简单到不简单【转】
转自:http://blog.csdn.net/coding__madman/article/details/51399353 版权声明:本文为博主原创文章,未经博主允许不得转载. 混杂设备驱动模型: ...
- pstack使用和原理【转】
转自:http://www.cnblogs.com/mumuxinfei/p/4366708.html 前言: 最近小组在组织<<深入剖析Nginx>>的读书会, 里面作者提到 ...