前篇 - 基本认证,用户名密码

后篇 - OAuth2 认证

由于宠物店的业务发展需要,我们需要一种更加便捷的方式来管理日益增多的宠物和客户。最好的方法就是开发一个APP,我可以用这个APP来添加、更新和删除宠物。同时,业务要给宠物店的会员用户有限查看某些宠物。

我们在开发中会用到NodeJs以及基于NodeJs的开发框架,如:Express,Mongoose(用来管理MongoDB的数据),Passport(认证)等工具。项目代码在这里

开始

我们这个项目的结构大概是这样的:

  1. petshot/ //服务端和客户端(android)
  2. server/ //服务端
  3. models/ //实体类
  4. pet.js
  5. user.js
  6. node_modules/ //npm安装的包,无需手动修改
  7. package.json //project定义和依赖声明
  8. server.js //服务端的一切从这里开始

注意node_modules这个目录你不需要创建,在执行npm安装命令之后这个目录会自动生成,并且npm命令会自动管理这个目录。

定义项目和依赖的包

这里假设你已经跳转到petshop/server/目录下了。之后在Terminal里执行命令:

  1. npm init

按照提示,依次在Terminal里输入相应的信息。最后npm命令生成package.json文件。文件是这样的:

  1. {
  2. "name": "petshop-server",
  3. "version": "0.1.0",
  4. "description": "petshop nodejs server",
  5. "main": "server.js",
  6. "dependencies": {
  7. }
  8. }

内容很多,这里是一部分。

安装所需要的包

为什么NodeJs能让那么多的开发者青睐有加,npm命令绝对也是一个数得上的原因了。下面就体会一下,npm命令。

首先,使用npm命令安装Express:

  1. npm install --save express

npm命令会选择合适的Express版本下载安装在本地。其他的包也是这么安装的。非常简单。

运行Server

有了Express,Server就已经可以运行起来了。如果你还没有创建server.js,请创建。在server.js中添加如下的代码:

  1. // 引入我们需要的包express
  2. var express = require('express');
  3. // 创建一个express的server
  4. var app = express();
  5. // server运行的端口号
  6. var port = process.env.PORT || '3090';
  7. // 使用express的路由器
  8. var router = express.Router();
  9. // 访问http://localhost:3090/api的时候,
  10. // 返回一个json
  11. router.get('/', function (req, res) {
  12. res.json({'message': '欢迎来到宠物商店'});
  13. });
  14. // 给路由设定根路径为/api
  15. app.use('/api', router);
  16. // 运行server,并监听指定的端口
  17. app.listen(port, function () {
  18. console.log('server is running at http://localhost:3090');
  19. });

通过require得到的express,就可以初始化出一个express的server。之后指定路由,并指定路由的相对根路径。在app上调用listen方法,并指定端口。这样express的server就运行起来了。

在Terminal中输入命令:

  1. 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框架。

  1. npm install --save mongoose

安装好之后在代码中引入(require)。

  1. var express = require('express');
  2. var mongoose = require('mongoose');

连接到数据库:

  1. // 连接数据库
  2. mongoose.connect('mongodb://localhost:27017/petshot');

创建model

MVC的开发模式这里就不再科普了。凡是MVC模式下开发,就一定会有Model,MVC的M。一般每一个model都和数据库中的一个“表”对应,在mongodb里“表”正式名称为“Collection”。我们这里只使用英文,专有名词没必要翻译。而“表”里的每一条“记录”又叫做“Document”。

我们首先在petshop/server/models/目录下新建一个文件pet.js。之后添加如下代码:

  1. // 1. 引入mongoose
  2. var mongoose = require('mongoose');
  3. var Schema = mongoose.Schema;
  4. // 2. 定义了Pet的Schema
  5. var petSchema = new Schema({
  6. name: {type: String, required: true},
  7. type: {type: String, required: true},
  8. quantity: Number
  9. });
  10. // 3. 定义并export了一个Model
  11. module.exports = mongoose.Model('pet', petSchema);

相关解释:

  1. 引入mongoose库。
  2. 定义了一个mongoose的Schema,这个Schema和一个mongodb的collection的定义相对应。这个schema有两个字符串和一个数字类型的属性。
  3. 把mongoose的Model export出去给server端的其他代码使用。哪里使用哪里就require这个model。

准备接收HTTP数据

首先,需要一个包(package)来解析http发送过来的数据。那么安装之:

  1. npm install --save body-parser

在代码中引入:

  1. var express = require('express');
  2. var mongoose = require('mongoose');
  3. // 稍后处理数据使用
  4. var Pet = require('./models/pet');
  5. // 解析http数据
  6. var bodyParser = require('body-parser');

下面还需要设置body-parser:

  1. var app = express();
  2. app.use(bodyParser.urlencoded({
  3. extended: true
  4. }));

添加些许宠物

我们的server终于迎来可以使用的一个功能了:给宠物商店添加宠物。

  1. router.get('/', function (req, res) {
  2. res.json({'message': '欢迎来到宠物商店'});
  3. });
  4. var petRouter = router.route('/pets');
  5. petRouter.post(function (req, res) {
  6. var pet = new Pet();
  7. pet.name = req.body.name;
  8. pet.type = req.body.type;
  9. pet.quantity = req.body.quantity;
  10. pet.save(function (err) {
  11. if (err) {
  12. res.json({message: 'error', data: err});
  13. return;
  14. }
  15. res.json({message: 'done', data: pet});
  16. });
  17. });

我们增加了一个/pets的相对路径来处理POST请求,并在请求中获取信息,把用这个信息来给初始化的pet的model赋值,并调用这个model的save方法保存数据到数据库。

打开postman,各个设置如图:

这样就可以往数据库添加pet数据了。

重构代码

现在的代码虽然已经可以添加宠物宝宝了。但是,后面如果我们还要添加其他功能,比如:查找,更新和删除等的时候,server.js文件势必会不断增加。这样给以后代码的维护带来困扰。所以我们要重构代码。

petshop/server/目录下添加controllers目录。根据MVC的模式开发,把处理业务方面的代码都存放在某个controller里。新建pet.js文件。这个文件就作为pet的controller。并将宠物的增删改查都放在这个文件中处理:

  1. var Pet = require('../models/pet');
  2. var postPets = function(req, res) {
  3. var pet = new Pet();
  4. pet.name = req.body.name;
  5. pet.type = req.body.type;
  6. pet.quantity = req.body.quantity;
  7. pet.save(function (err) {
  8. if (err) {
  9. res.json({message: 'error', data: err});
  10. return;
  11. }
  12. res.json({message: 'done', data: pet});
  13. });
  14. };
  15. var getPets = function(req, res) {
  16. Pet.find(function (err, pets) {
  17. if (err) {
  18. res.json({message: 'error', data: err});
  19. return;
  20. }
  21. res.json({message: 'done', data: pets});
  22. });
  23. };
  24. var getPet = function(req, res) {
  25. Pet.findById(req.params.pet_id, function (err, pet) {
  26. if (err) {
  27. res.json({message: 'error', data: err});
  28. return;
  29. }
  30. res.json({message: 'done', data: pet});
  31. });
  32. };
  33. var updatePet = function(req, res) {
  34. Pet.findById(req.params.pet_id, function(err, pet) {
  35. if (err) {
  36. res.json({message: 'error', data: err});
  37. return;
  38. }
  39. pet.quantity = req.params.quantity;
  40. pet.save(function (err) {
  41. if (err) {
  42. res.json({message: 'error', data: err});
  43. return;
  44. }
  45. res.json({message: 'done', data: pet});
  46. });
  47. });
  48. };
  49. var deletePet = function(req, res) {
  50. Pet.findByIdAndRemove(req.params.pet_id, function(err) {
  51. if (err) {
  52. res.json({message: 'error', data: err});
  53. return;
  54. }
  55. res.json({message: 'done', data: {}});
  56. });
  57. }
  58. module.exports = {
  59. postPets: postPets,
  60. getPets: getPets,
  61. getPet: getPet,
  62. updatePet: updatePet,
  63. deletePet: deletePet
  64. };

原来的server.js也需要重构:

  1. var Pet = require('../models/pet');
  2. var postPets = function(req, res) {
  3. var pet = new Pet();
  4. pet.name = req.body.name;
  5. pet.type = req.body.type;
  6. pet.quantity = req.body.quantity;
  7. pet.save(function (err) {
  8. if (err) {
  9. res.json({message: 'error', data: err});
  10. return;
  11. }
  12. res.json({message: 'done', data: pet});
  13. });
  14. };
  15. var getPets = function(req, res) {
  16. Pet.find(function (err, pets) {
  17. if (err) {
  18. res.json({message: 'error', data: err});
  19. return;
  20. }
  21. res.json({message: 'done', data: pets});
  22. });
  23. };
  24. var getPet = function(req, res) {
  25. Pet.findById(req.params.pet_id, function (err, pet) {
  26. if (err) {
  27. res.json({message: 'error', data: err});
  28. return;
  29. }
  30. res.json({message: 'done', data: pet});
  31. });
  32. };
  33. var updatePet = function(req, res) {
  34. Pet.findById(req.params.pet_id, function(err, pet) {
  35. if (err) {
  36. res.json({message: 'error', data: err});
  37. return;
  38. }
  39. pet.quantity = req.params.quantity;
  40. pet.save(function (err) {
  41. if (err) {
  42. res.json({message: 'error', data: err});
  43. return;
  44. }
  45. res.json({message: 'done', data: pet});
  46. });
  47. });
  48. };
  49. var deletePet = function(req, res) {
  50. Pet.findByIdAndRemove(req.params.pet_id, function(err) {
  51. if (err) {
  52. res.json({message: 'error', data: err});
  53. return;
  54. }
  55. res.json({message: 'done', data: {}});
  56. });
  57. }
  58. module.exports = {
  59. postPets: postPets,
  60. getPets: getPets,
  61. getPet: getPet,
  62. updatePet: updatePet,
  63. deletePet: deletePet
  64. };

认证

我们当让不行让谁都可以添加宠物宝宝了。查看是可以的,添加需要控制。

passport

Passport就是给基于Express开发的web应用的,专注于认证中间件。也有和body-parser相类似的使用方法。passport的功能非常丰富,不过我们先使用最简单的一种认证策略

安装:

  1. npm install --save passport-http

认证以前首先要有用户数据。

同时还有一个包需要安装:

  1. npm install --save bcrypt-nodejs

这个包是用来给密码hash用的。

用户model

所有关于用户的数据都放在MongoDB的user colleciton里,并有user model与之对应。在models目录下新建user.js文件。

  1. var mongoose = require('mongoose'),
  2. bcrypt = require('bcrypt-nodejs');
  3. var Schema = mongoose.Schema;
  4. var userSchema = new Schema({
  5. username: {type: String, unique: true, required: true},
  6. password: {type: String, required: true}
  7. });
  8. // * called before 'save' method.
  9. userSchema.pre('save', function (next) {
  10. var self = this;
  11. if (!self.isModified('password')) {
  12. return next();
  13. }
  14. bcrypt.genSalt(5, function (err, salt) {
  15. if (err) {
  16. return next(err);
  17. }
  18. bcrypt.hash(self.password, salt, null, function (err, hash) {
  19. if (err) {
  20. return next(err);
  21. }
  22. self.password = hash;
  23. next();
  24. });
  25. });
  26. });
  27. 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的用户密码

  1. var User = require('../models/user');
  2. var postUsers = function (req, res) {
  3. var user = new User({
  4. username: req.body.username,
  5. password: req.body.password
  6. });
  7. user.save(function (err) {
  8. if (err) {
  9. res.json({message: 'error', data: err});
  10. return;
  11. }
  12. res.json({message: 'done', data: user});
  13. });
  14. };
  15. var getUsers = function (req, res) {
  16. User.find(function (err, users) {
  17. if (err) {
  18. res.json({message: 'error', data: err});
  19. return;
  20. }
  21. res.json({message: 'done', data: users});
  22. });
  23. };
  24. module.exports = {
  25. postUsers: postUsers,
  26. getUsers: getUsers
  27. };

定义user controller的路由:

  1. ...
  2. var petController = require('./controllers/pet')
  3. userController = require('./controllers/user');
  4. ...
  5. // path: /users, for users
  6. router.route('/users')
  7. .post(userController.postUsers)
  8. .get(userController.getUsers);

你已经可以在这个路径:http://localhost:3090/api/users下POST添加用户,GET获取全部用户。

认证

在开始以前首先确保你已经安装了认证需要的包:

  1. npm install --save passport
  2. npm install --save passport-http

之后给在user model里添加一个方法验证password:

  1. userSchema.methods.verifyPassword = function (password, callback) {
  2. bcrypt.compare(password, this.password, function (err, match) {
  3. if (err) {
  4. return callback(err);
  5. }
  6. callback(null, match);
  7. });
  8. };

接下来,在controllers目录下添加auth.js文件。

  1. var passport = require('passport'),
  2. BasicStrategy = require('passport-http').BasicStrategy,
  3. User = require('../models/user');
  4. passport.use(new BasicStrategy(
  5. function(username, password, done) {
  6. User.findOne({username: username}, function(err, user) {
  7. if (err) {
  8. return done(err);
  9. }
  10. // 用户不存在
  11. if (!user) {
  12. return done(null, false);
  13. }
  14. // 检查用户的密码
  15. user.verifyPassword(passowrd, function(err, match) {
  16. // 密码不匹配
  17. if (!match) {
  18. return done(null, false);
  19. }
  20. // 成功
  21. return done(null, user);
  22. });
  23. });
  24. }
  25. ));
  26. module.exports.isAuthenticated = passport.authenticate('basic', {session: false});

我们使用包passport-httpBasicStrategy来处理http的用户认证。首先,我们通过用户名查找用户。如果用户存在,接着验证用户的密码是否与数据库的数据一致。如果以上两步通过验证则用户认证成功,否则不成功。

最后一句就是告知passport使用BasicStrategy来认证用户。session为false,是告诉passport不存储用户的session。用户每一次的http请求都需要提供用户名和密码。

相应的更新server.js:

  1. // 引入我们需要的包express
  2. var express = require('express'),
  3. mongoose = require('mongoose'),
  4. bodyParser = require('body-parser'),
  5. passport = require('passport'),
  6. petController = require('./controllers/pet'),
  7. userController = require('./controllers/user'),
  8. authController = require('./controllers/auth');
  9. // 创建一个express的server
  10. var app = express();
  11. app.use(bodyParser.urlencoded({
  12. extended: true
  13. }));
  14. // 连接数据库
  15. mongoose.connect('mongodb://localhost:27017/petshot');
  16. ...
  17. router.route('/pets')
  18. .post(authController.isAuthenticated, petController.postPets)
  19. .get(authController.isAuthenticated, petController.getPets);
  20. router.route('/pets/:pet_id')
  21. .get(authController.isAuthenticated, petController.getPet)
  22. .put(authController.isAuthenticated, petController.updatePet)
  23. .delete(authController.isAuthenticated, petController.deletePet);
  24. // path: /users, for users
  25. router.route('/users')
  26. .post(userController.postUsers)
  27. .get(authController.isAuthenticated, userController.getUsers);
  28. ...

如果还没有数据的话,首先使用POST方法添加几个用户。之后GET用户测试一下。如果用户名、密码都对的话就会获得数据了。

使用passport包认证还有一个好处,你可以直接从req获取user数据。如:req.user._id获得用户的_id。有些数据需要记录更新数据的用户,这样就非常方便了。

最后

下文使用更加安全的oauth2认证。

基于Node的PetShop,RESTful API以及认证的更多相关文章

  1. RESTful Api 身份认证安全性设计

    REST是一种软件架构风格.RESTful Api 是基于 HTTP 协议的 Api,是无状态传输.它的核心是将所有的 Api 都理解为一个网络资源.将所有的客户端和服务器的状态转移(动作)封装到 H ...

  2. 关于RESTFUL API 安全认证方式的一些总结

    常用认证方式 在之前的文章REST API 安全设计指南与使用 AngularJS & NodeJS 实现基于 token 的认证应用两篇文章中,[译]web权限验证方法说明中也详细介绍,一般 ...

  3. RESTful Api 身份认证中的安全性设计探讨

    REST 是一种软件架构风格.RESTful Api 是基于 HTTP 协议的 Api,是无状态传输.它的核心是将所有的 Api 都理解为一个网络资源.将所有的客户端和服务器的状态转移(动作)封装到 ...

  4. 使用 node-odata 轻松创建基于 OData 协议的 RESTful API

    前言 OData, 相信身为.NET程序员应该不为陌生, 对于他的实现, 之前也有童鞋进行过介绍(见:这里1,这里2). 微软的WCF Data Service即采用的该协议来进行通信, ASP.NE ...

  5. 关于 RESTFUL API 安全认证方式的一些总结

    常用认证方式 在之前的文章REST API 安全设计指南与使用 AngularJS & NodeJS 实现基于 token 的认证应用两篇文章中,[译]web权限验证方法说明中也详细介绍,一般 ...

  6. 基于Node的PetShop,oauth2认证RESTful API

    前篇 - 基本认证,用户名密码 后篇 - OAuth2 认证 前文使用包passport实现了一个简单的用户名.密码认证.本文改用oauth2来实现更加安全的认证.全部代码在这里. OAUTH2 用户 ...

  7. Node.js实现RESTful api,express or koa?

    文章导读: 一.what's RESTful API 二.Express RESTful API 三.KOA RESTful API 四.express还是koa? 五.参考资料 一.what's R ...

  8. Node.js:RESTful API

    ylbtech-Node.js:RESTful API 1.返回顶部 1. Node.js RESTful API 什么是 REST? REST即表述性状态传递(英文:Representational ...

  9. 基于Spring Boot的RESTful API实践(一)

    1. RESTful简述    REST是一种设计风格,是一组约束条件及原则,而遵循REST风格的架构就称为RESTful架构,资源是RESTful的核心,一个好的RESTful架构,通过URL就能很 ...

随机推荐

  1. shell学习笔记(2)替换命令··与()的区别

    ·CMD·在执行的时候,shell不管··中的内容是什么,先进性解释,再把解释后的最终结果送给shell,如果解释后的结果不是shell可以行的命令,就会报错.但是仅仅把cmd的执行结果作为文本输出, ...

  2. 深度解析 Java 内存原型

    一.Java 虚拟机内存原型 寄存器:我们在程序中无法控制. 栈:存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在 堆中. 堆:存放用 new 产生的数据. 静态域:存放在对象中用  ...

  3. android复习第二天------布局

    1,在4,0版本前一共有五种布局,且都是ViewGroup的子类分别是 RelativeLayout(相对),AbsoluteLayout(绝对),LinearLayout(线性),FrameLayo ...

  4. c#读取INI文件类

    using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.IO;na ...

  5. sql查询 所有被锁定的表

    --sql查询  所有被锁定的表 select request_session_id spid,OBJECT_NAME(resource_associated_entity_id) tableName ...

  6. Linux centOS7 下安装mysql5.7.10

    1:下载二进制安装包 http://cdn.mysql.com//Downloads/MySQL-5.7/mysql-5.7.10-linux-glibc2.5-x86_64.tar.gz 2:解压到 ...

  7. android 自定义view详解

    1.自定义View前首先要了解一下View的方法,虽然有些不一定要实现. 分类 方法 描述 创建 Constructors View中有两种类型的构造方法,一种是在代码中构建View,另一种是填充布局 ...

  8. SqlServer:此数据库处于单用户模式,导致数据库无法删除的处理

    此数据库处理单用户模式,尚在连接当中,无法删除(既使将SQLServer停止后再启动也是如此) USE [master] GO /****** Object: StoredProcedure [dbo ...

  9. android 项目学习随笔四(优化ViewPager)

    1.不能滑动的ViewPager import android.content.Context; import android.support.v4.view.ViewPager; import an ...

  10. Linux设备驱动工程师之路——内核链表的使用【转】

    本文转载自:http://blog.csdn.net/forever_key/article/details/6798685 Linux设备驱动工程师之路——内核链表的使用 K-Style 转载请注明 ...