• 基于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);
    

    相关解释:

    1. 引入mongoose库。
    2. 定义了一个mongoose的Schema,这个Schema和一个mongodb的collection的定义相对应。这个schema有两个字符串和一个数字类型的属性。
    3. 把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-httpBasicStrategy来处理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认证。

  • 相关阅读:
    SQL注入实验-2021.01.24
    数据库的搭建与基本语句2021-01-24
    Linux
    磁盘配置
    在Vmware中Centos下的Hadoop环境搭建
    Linux系统(CentOS)-2021.1.19
    中间件,JavaScript,PHP及burpSuite暴力破解实验-2021.1.16
    html,css学习笔记-2021.1.15
    第一周学习视频(二)
    第一周学习视频(一)
  • 原文地址:https://www.cnblogs.com/sunshine-anycall/p/5634907.html
Copyright © 2020-2023  润新知