如何建立JavaScript文件?

196 阅读10分钟

因此,我们将开始在Node.js中构建最基本的用户注册系统,使用MongoDB作为数据存储,Express作为路由系统,Joi作为验证器,当然还有Mongoose来使Node与Mongo的交互变得简单。下面是我们的项目布局示例。用户注册是顶层目录,里面有index.js文件,然后我们有一个模型目录和一个路由目录。我们现在要看看如何建立JavaScript文件,以使其发挥作用。

user registration tutorial project


第1步:创建一个用户模型

首先,我们需要创建一个用户模型。你可以创建一个user.js 文件并把它放在models目录中。在文件的顶部,我们需要Joi和Mongoose,因为我们将需要它们来验证和创建用户Mongodb模式。然后,我们创建用户模式并定义姓名、电子邮件和密码的要求。用户模式被存储在User 。接下来,我们创建一个验证函数,名为validateUser 。最后,我们导出这些模块,这样我们就可以在其他地方要求它们。


/models/user.js

const Joi = require('joi');
const mongoose = require('mongoose');

const User = mongoose.model('User', new mongoose.Schema({
    name: {
        type: String,
        required: true,
        minlength: 5,
        maxlength: 50
    },
    email: {
        type: String,
        required: true,
        minlength: 5,
        maxlength: 255,
        unique: true
    },
    password: {
        type: String,
        required: true,
        minlength: 5,
        maxlength: 1024
    }
}));

function validateUser(user) {
    const schema = {
        name: Joi.string().min(5).max(50).required(),
        email: Joi.string().min(5).max(255).required().email(),
        password: Joi.string().min(5).max(255).required()
    };
    return Joi.validate(user, schema);
}

exports.User = User;
exports.validate = validateUser;

第2步:设置用户路线

现在我们已经建立了一个用户模型,定义了我们需要遵循的模式和验证规则,我们可以在我们的routes目录中创建一个users.js 路由文件。在这个文件中,我们在顶部做的第一件事就是要求或导入我们刚刚在user.js中导出的用户模式和验证模式。接下来我们要确保Express被初始化。router.post()函数在这里完成了所有繁重的工作。首先,http post请求被验证,然后我们检查用户是否已经存在于数据库中,最后我们创建一个新的用户,如果他们不存在于数据库中,而且他们也通过了所有的验证要求。最后,我们导出路由器,所以我们可以在index.js文件中使用它。


/routes/users.js

const { User, validate } = require('../models/user');
const express = require('express');
const router = express.Router();

router.post('/', async (req, res) => {
    // First Validate The Request
    const { error } = validate(req.body);
    if (error) {
        return res.status(400).send(error.details[0].message);
    }

    // Check if this user already exisits
    let user = await User.findOne({ email: req.body.email });
    if (user) {
        return res.status(400).send('That user already exisits!');
    } else {
        // Insert the new user if they do not exist yet
        user = new User({
            name: req.body.name,
            email: req.body.email,
            password: req.body.password
        });
        await user.save();
        res.send(user);
    }
});

module.exports = router;

第3步:在index.js中注册用户路由


index.js

如果你已经学习了rest api教程,这里的大部分模板对你来说应该很熟悉。这里强调了本教程的关键点。注意我们在第4行需要 users.js 文件。这使我们能够在第13行为/api/users 设置路由。

const Joi = require('joi');
Joi.objectId = require('joi-objectid')(Joi);
const mongoose = require('mongoose');
const users = require('./routes/users');
const express = require('express');
const app = express();

mongoose.connect('mongodb://localhost/mongo-games')
    .then(() => console.log('Now connected to MongoDB!'))
    .catch(err => console.error('Something went wrong', err));

app.use(express.json());
app.use('/api/users', users);

const port = process.env.PORT || 4000;
app.listen(port, () => console.log(`Listening on port ${port}...`));

第4步:用Postman测试Post请求

现在我们可以利用Postman向我们的服务器发送一个Post请求,看看我们是否能将一个新用户持久化到MongoDB。首先,让我们启动应用程序。

user-registration $node index.js
Listening on port 4000...
Now connected to MongoDB!

真棒,一切都在运行,没有崩溃!现在我们可以测试一些Post请求。在这里,我们以application/json的形式发送一个post请求,请求的主体是一个json对象。我们只设置了用户名,但我们没有设置电子邮件和密码。我们可以看到,我们的验证是有效的,因为我们从服务器上得到的响应是*"email "是必需的。*
validation is working

现在让我们填写一个适当的用户对象,看看我们是否能让用户存储在MongoDB数据库中。这一次,我们没有得到一个错误的反馈,但我们看到了用户对象。这意味着它是成功的!
post request to express with json

现在我们可以使用Compass查看MongoDB内部,看看这个新用户是否已经到位。很好!
new user listed in mongodb

回想一下,我们确实在代码中加入了一些逻辑,以确保如果数据库中已经有一个用户,那么我们就不应该再坚持这个用户。为了测试这一点,我们再次向服务器发送相同的请求,我们得到了我们期望的响应。我们不允许两次插入同一个用户。非常好!
check for existing user before new registration


用Bcrypt哈希密码

用户注册的基本部分现在可以工作了,但是密码是明文的。这是一个很大的问题,所以让我们看看如何在使用bcrypt包保存到数据库之前对密码进行加密。首先,我们安装它。

user-registration $npm i bcrypt

> bcrypt@2.0.1 install C:nodeuser-registrationnode_modulesbcrypt
> node-pre-gyp install --fallback-to-build

[bcrypt] Success: "C:nodeuser-registrationnode_modulesbcryptlibbindingbcrypt_lib.node" is installed via remote

+ bcrypt@2.0.1
added 69 packages from 47 contributors and audited 247 packages in 8.548s
found 1 low severity vulnerability
  run `npm audit fix` to fix them, or `npm audit` for details

现在我们已经安装了bcrypt,我们可以像这样在 users.js 路由文件中使用它。在文件的顶部,我们现在需要bcrypt包,这使得它可以在文件的下面使用。在第24行和第25行,我们生成一个盐,并在保存前使用它来散列密码。


/routes/users.js

const bcrypt = require('bcrypt');
const { User, validate } = require('../models/user');
const express = require('express');
const router = express.Router();

router.post('/', async (req, res) => {
    // First Validate The Request
    const { error } = validate(req.body);
    if (error) {
        return res.status(400).send(error.details[0].message);
    }

    // Check if this user already exisits
    let user = await User.findOne({ email: req.body.email });
    if (user) {
        return res.status(400).send('That user already exisits!');
    } else {
        // Insert the new user if they do not exist yet
        user = new User({
            name: req.body.name,
            email: req.body.email,
            password: req.body.password
        });
        const salt = await bcrypt.genSalt(10);
        user.password = await bcrypt.hash(user.password, salt);
        await user.save();
        res.send(user);
    }
});

module.exports = router;

现在,让我们运行该应用程序,然后用Postman进行测试。

user-registration $node index.js
Listening on port 4000...
Now connected to MongoDB!

有了这个,我们就可以打开Postman,[向http://localhost:4000/api/users/,并在请求正文中指定一个新的用户作为JSON对象,发送POST请求。
hash password user registration node

好极了!我们得到一个响应对象,这意味着一个新的用户被创建,并注意到密码字段。它是完全散列的。这样一来,密码在Mongo数据库中是安全的。事实上,让我们也用Compass来检查它。请注意,我们创建的第一个用户的密码是以纯文本存储的。新的用户有一个更安全的密码,它使用bcrypt进行了适当的散列。
password is now hashed in mongodb


使用Lodash来简化我们的代码

让我们继续把lodash包导入我们的项目,这样我们就可以利用它了。Lodash是一个强大的JavaScript工具库,类似于流行的Underscore库。在这里,我们继续安装Lodash。

user-registration $npm i lodash

+ lodash@4.17.10
updated 1 package and audited 247 packages in 13.631sfound 1 low severity vulnerability
  run `npm audit fix` to fix them, or `npm audit` for details

很好!现在我们可以在我们的项目中使用Lodash。具体来说,在这个例子中,我们要使用pick函数,它使处理对象的工作更加简明。现在,一旦我们把lodash导入到我们的文件中,我们就可以使用这里强调的这些方便的单行文字。

const bcrypt = require('bcrypt');
const _ = require('lodash');
const { User, validate } = require('../models/user');
const express = require('express');
const router = express.Router();

router.post('/', async (req, res) => {
    // First Validate The Request
    const { error } = validate(req.body);
    if (error) {
        return res.status(400).send(error.details[0].message);
    }

    // Check if this user already exisits
    let user = await User.findOne({ email: req.body.email });
    if (user) {
        return res.status(400).send('That user already exisits!');
    } else {
        // Insert the new user if they do not exist yet
        user = new User(_.pick(req.body, ['name', 'email', 'password']));
        const salt = await bcrypt.genSalt(10);
        user.password = await bcrypt.hash(user.password, salt);
        await user.save();
        res.send(_.pick(user, ['_id', 'name', 'email']));
    }
});

module.exports = router;

如何认证用户

现在,用户注册已经到位,我们可以设置验证用户的过程了。首先,在路由目录中创建一个 auth.js 文件。一旦完成,我们可以用这个模板开始。


/routes/auth.js

const Joi = require('joi');
const bcrypt = require('bcrypt');
const _ = require('lodash');
const { User } = require('../models/user');
const express = require('express');
const router = express.Router();

router.post('/', async (req, res) => {

});

module.exports = router;

现在我们要回到index.js文件,像这样为'api/auth'设置路由。

const Joi = require('joi');
Joi.objectId = require('joi-objectid')(Joi);
const mongoose = require('mongoose');
const users = require('./routes/users');
const auth = require('./routes/auth');
const express = require('express');
const app = express();

mongoose.connect('mongodb://localhost/mongo-games')
    .then(() => console.log('Now connected to MongoDB!'))
    .catch(err => console.error('Something went wrong', err));

app.use(express.json());
app.use('/api/users', users);
app.use('/api/auth', auth);

const port = process.env.PORT || 4000;
app.listen(port, () => console.log(`Listening on port ${port}...`));

好了,回到 auth.js 文件。在这里,我们需要设置逻辑,当用户在尝试登录时提供凭证时,将对其进行认证。这意味着我们需要验证正在发送的HTTP请求,在数据库中找到用户,然后使用bcrypt来比较存储的密码和请求中提供的密码。这段代码将实现这些目标。


/routes/auth.js

const Joi = require('joi');
const bcrypt = require('bcrypt');
const _ = require('lodash');
const { User } = require('../models/user');
const express = require('express');
const router = express.Router();

router.post('/', async (req, res) => {
    // First Validate The HTTP Request
    const { error } = validate(req.body);
    if (error) {
        return res.status(400).send(error.details[0].message);
    }

    //  Now find the user by their email address
    let user = await User.findOne({ email: req.body.email });
    if (!user) {
        return res.status(400).send('Incorrect email or password.');
    }

    // Then validate the Credentials in MongoDB match
    // those provided in the request
    const validPassword = await bcrypt.compare(req.body.password, user.password);
    if (!validPassword) {
        return res.status(400).send('Incorrect email or password.');
    }

    res.send(true);
});

function validate(req) {
    const schema = {
        email: Joi.string().min(5).max(255).required().email(),
        password: Joi.string().min(5).max(255).required()
    };

    return Joi.validate(req, schema);
}

module.exports = router; 

非常好!现在让我们用Postman测试一下auth端点。我们可以提供一个有效的电子邮件和密码,看看会发生什么。
successful user authentication nodejs

现在让我们用错误的密码发送一个请求,看看结果。啊哈,看起来不错!它捕捉到了错误的密码,因此用户不能进行认证。
incorrect email or password


实现JSON网络令牌

在上面的章节中,我们只是在登录尝试成功后返回一个true 。现在我们要修改这个响应,以发送一个JSON网络令牌,它可以唯一地识别系统中的任何特定用户。因此,一般来说,它的工作方式是,API在成功登录后生成一个JSON网络令牌,然后在未来,当向api发出各种http请求时,用户必须提供JSON网络令牌来识别他们是一个有效的用户。在客户端,这个令牌可以被存储在本地存储中。这超出了本教程的范围,因为我们在这里将专注于服务器端。好了,为了开始生成JSON网络令牌,我们需要安装一个npm包来为我们处理这个问题。

user-registration $npm i jsonwebtoken

+ jsonwebtoken@8.3.0
added 13 packages from 9 contributors and audited 263 packages in 4.659sfound 1 low severity vulnerability
  run `npm audit fix` to fix them, or `npm audit` for details

在这里,我们修改auth.js文件,以利用jsonwebtoken包。我们还使用它来生成一个新的JSON Web Token,并将其作为一个适当的http请求的响应发送回来。

const jwt = require('jsonwebtoken');
const Joi = require('joi');
const bcrypt = require('bcrypt');
const _ = require('lodash');
const { User } = require('../models/user');
const express = require('express');
const router = express.Router();

router.post('/', async (req, res) => {
    // First Validate The HTTP Request
    const { error } = validate(req.body);
    if (error) {
        return res.status(400).send(error.details[0].message);
    }

    //  Now find the user by their email address
    let user = await User.findOne({ email: req.body.email });
    if (!user) {
        return res.status(400).send('Incorrect email or password.');
    }

    // Then validate the Credentials in MongoDB match
    // those provided in the request
    const validPassword = await bcrypt.compare(req.body.password, user.password);
    if (!validPassword) {
        return res.status(400).send('Incorrect email or password.');
    }
    const token = jwt.sign({ _id: user._id }, 'PrivateKey');
    res.send(token);
});

function validate(req) {
    const schema = {
        email: Joi.string().min(5).max(255).required().email(),
        password: Joi.string().min(5).max(255).required()
    };

    return Joi.validate(req, schema);
}

module.exports = router; 

太棒了让我们测试一下向我们的/api/auth端点发送一个有效的用户名和电子邮件的POST请求。我们看到,一个有效的JSON Web Token被返回给我们。
api generated json web token

我们真的不应该把PrivateKey作为源代码的一部分,它应该在某种环境变量中。现在让我们来做这个。首先,我们可以安装配置包。

user-registration $npm i config

+ config@1.30.0
added 3 packages from 5 contributors and audited 266 packages in 5.314sfound 1 low severity vulnerability
  run `npm audit fix` to fix them, or `npm audit` for details

一旦安装,我们就可以在 auth.js 文件中要求它。

const config = require('config');

现在让我们在我们的项目中建立一个config文件夹,并在其中放置一个default.json文件和一个custom-environment-variables.json文件。
default.json

{
  "PrivateKey": ""
}

custom-environment-variables.json

{
  "PrivateKey": "PrivateKey"
}

现在,我们不再直接引用私钥,而是使用config.get()函数引用它,就像我们在这里看到的那样。

const token = jwt.sign({ _id: user._id }, config.get('PrivateKey'));

我们也应该像这样把它包含在index.js中。

const config = require('config');
const Joi = require('joi');
Joi.objectId = require('joi-objectid')(Joi);
const mongoose = require('mongoose');
const users = require('./routes/users');
const auth = require('./routes/auth');
const express = require('express');
const app = express();

if (!config.get('PrivateKey')) {
    console.error('FATAL ERROR: PrivateKey is not defined.');
    process.exit(1);
}

mongoose.connect('mongodb://localhost/mongo-games')
    .then(() => console.log('Now connected to MongoDB!'))
    .catch(err => console.error('Something went wrong', err));

app.use(express.json());
app.use('/api/users', users);
app.use('/api/auth', auth);

const port = process.env.PORT || 4000;
app.listen(port, () => console.log(`Listening on port ${port}...`));

最后,我们需要使用类似这样的方法来设置密钥。

user-registration $export PrivateKey=SecureAF

设置响应头信息

在上面的部分,我们成功地生成了一个JSON Web Token,并在响应的主体中把它送回给客户端。现在我们可以做一些调整,在响应的头信息中发送令牌,这是一个更常见的情况。我们可以在auth.js文件中为新用户注册时这样做。

const jwt = require('jsonwebtoken');
const config = require('config');
const bcrypt = require('bcrypt');
const _ = require('lodash');
const { User, validate } = require('../models/user');
const express = require('express');
const router = express.Router();

router.post('/', async (req, res) => {
    // First Validate The Request
    const { error } = validate(req.body);
    if (error) {
        return res.status(400).send(error.details[0].message);
    }

    // Check if this user already exisits
    let user = await User.findOne({ email: req.body.email });
    if (user) {
        return res.status(400).send('That user already exisits!');
    } else {
        // Insert the new user if they do not exist yet
        user = new User(_.pick(req.body, ['name', 'email', 'password']));
        const salt = await bcrypt.genSalt(10);
        user.password = await bcrypt.hash(user.password, salt);
        await user.save();
        const token = jwt.sign({ _id: user._id }, config.get('PrivateKey'));
        res.header('x-auth-token', token).send(_.pick(user, ['_id', 'name', 'email']));
    }
});

module.exports = router; 

现在让我们启动应用程序并从Postman创建一个新的用户。

user-registration $node index.js
Listening on port 4000...
Now connected to MongoDB!

在Postman中,当我们发送一个创建新用户的请求,然后检查响应头,我们可以看到我们生成的JSON Web Token。
json web token in response header

现在在客户端,这个头信息可以被读取和存储,用于随后所有从客户端对服务器进行的API调用。


Node.js MongoDB用户注册总结

在本教程中,我们介绍了为由Node.js、Express和MongoDB驱动的REST API设置用户注册和授权的基本情况。这仅仅是为了学习,而不是为现实世界中的任何应用提供动力的代码以下是我们学到的内容。

  • 认证处理的是通过检查电子邮件和密码来确定用户是否是他或她声称的人。
  • 授权决定用户是否有权限执行某些操作。
  • 你应该使用像bcrypt这样的软件包对密码进行散列。
// To Hash a Password
const salt = await bcrypt.genSalt(10);
const hashed = await bcrypt.hash('abc123', salt);

// Validating passwords
const isValid = await bcrypt.compare(‘abc123’, hashed);
  • 一个JSON网络令牌是一个编码为长字符串的JSON对象。它们被用来识别用户。JWT在其有效载荷中可能包括一些关于用户的公共属性。这些属性不能被篡改,因为这样做需要重新生成数字签名。
  • 当用户登录时,你可以在服务器上生成一个JWT并将其返回给客户端。然后,客户端可以将这个令牌用于所有未来的API请求。
  • 要生成JSON网络令牌,你可以使用jsonwebtoken 包。
// Generating a JWT
const jwt = require(‘jsonwebtoken’);
const token = jwt.sign({ _id: user._id}, 'privateKey');
  • 不要在你的代码库中存储私钥。它们应该被存储在环境变量中。然后可以使用config包来读取存储在环境变量中的应用程序设置。
  • 没有必要在服务器上实现注销。它只需要在客户端设置,简单地从本地存储中删除JWT。
  • 不要在数据库中以纯文本形式存储JSON Web令牌。JSON Web令牌应该存储在客户端。如果绝对有必要将其存储在服务器上,请确保在将其存储到数据库之前对其进行加密。