使用 PostgreSQL 在 NodeJS 中进行用户授权
用户身份验证是开发 Web 应用程序时不可或缺的部分之一。虽然我们可以使用Firebase或Passport等第三方应用程序来验证用户身份,但在本博客中,我们将使用一种简单且自主开发的方法,即使用 NodeJS 和 PostgreSQL 创建 REST API,这将帮助我们理解身份验证的基础知识。
先决条件和使用的技术
- nodeJS
- nodemon
- 表达
- 科尔斯
- 前列腺素
- 加密
- jwt
- dotenv
另外最好在ElephantSQL上拥有一个帐户。
基本结构
设置好所有依赖项后,您可以先创建一个文件夹结构。这里我们将使用 MVC 文件夹结构,即模型 --> 视图 --> 控制器的结构。我们将为每个任务创建不同的文件夹。
在根目录中运行以下命令
touch server.js
mkdir routes controller routes configs
这将创建我们需要授权用户的所有文件夹。server.js 将是我们的根文件,我们将在 ExpressJS 的帮助下启动我们的服务器。
对于一些人来说,这可能有点令人望而生畏,但坚持到最后,你一定会明白如何创建你的服务器,以及如何通过它授权和验证应用程序的新用户。
启动我们的服务器
在我们的主 server.js 文件中,我们将构建服务器。
我们的 server.js 应该如下所示
const express = require("express");
const app = express(); //Initialized express
app.use(express.json());
app.use(cors());
const port = process.env.PORT || 5000;
app.get("/", (req, res) => {
res.status(200).send("Engine Started, Ready to take off!");
})
app.listen(port, () => {
console.log(`Here we go, Engines started at ${port}.`);
})
这里我们添加了cors,它支持跨源资源共享。您可以在这里了解更多信息。
这里,我们process.env.port会查找任何空闲端口来本地运行我们的服务器,如果我们的端口不是空闲的,例如本例中的 5000 端口。我们可以通过运行localhost:5000来检查服务器是否正常运行。
PostgresSQL 中的“用户”表模式
现在,为了创建用户表,我们也可以在本地运行 Postgres,但在本博客中,我们将使用 ElephantSQL 来简化将其与后端的远程连接。在 ElephantSQL 上创建帐户后,将数据库 URL 从详细信息部分复制到剪贴板。
我们需要运行以下 SQL 查询来创建用户表。
CREATE TABLE "users" (
"id" SERIAL PRIMARY KEY,
"name" text NOT NULL,
"email" text NOT NULL UNIQUE,
"phonenumber" text NOT NULL UNIQUE,
"password" varchar NOT NULL,
);
自行注册的用户应拥有唯一的电子邮件和唯一的电话号码,以防止同一用户多次注册以及用户机器人的垃圾邮件。
现在我们将拥有用户表,可以在其中存储用户信息。
使用我们的 Node 应用程序
现在我们已经构建了数据库,我们需要配置它并将其连接到服务器。在项目目录中,我们将直接进入configs目录并创建两个文件database.js和dotenv.js。
在添加我们的 Postgres 凭证之前,我们将在根目录中创建一个新文件 .env,将所有值存储为环境变量,这样如果任何其他人获得我们的代码,他们将无法访问我们的凭证。
在我们的 .env 中,添加以下几行
DB_URL = paste your database URL here
现在在我们的dotenv.js中我们将添加以下几行
if (process.env.NODE_ENV !== 'production') {
require('dotenv').config() //Configuring dotenv during development stage
}
这意味着如果我们处于开发阶段,服务器必须配置我们的 dotenv 文件。
现在要将我们的数据库连接到后端,我们必须在database.js中添加这些行来配置我们的 postgres 数据库。
const { Client } = require("pg");
const client = new Client(process.env.DB_URL); //Configuring PostgresSQL Database
module.exports = client;
现在我们已经配置了数据库,为了将其连接到我们的数据库,我们必须将这些行添加到我们的server.js中。
require("./configs/dotenv");
const client = require("./configs/database");
client.connect((err) => { //Connected Database
if (err) {
console.log(err);
}
else {
console.log("Data logging initiated!");}
});
通过我们的控制台,我们将知道我们是否连接到本地主机上的数据库。
创建并路由我们的用户端点
在我们的server.js中,我们将添加以下代码行,只是为了告诉服务器,每当我们获取 /users 时,它必须运行我们的用户方法。
const user = require("./routes/user");
app.use("/user", user); //Route for /user endpoint of API
在这里我们可以为不同的任务创建多个端点。
我们将在下一节中了解什么是 routes/user。
现在在我们的routes目录中,我们将创建users.js并添加以下代码
const express = require('express');
const router = express.Router();
const {register} = require("../controllers/register");
const {login} = require("../controllers/login");
router.post('/register' , register); //POST request to register the user
router.post('/login' , login); // POST request to login the user
module.exports = router;
这里我们告诉服务器转到这些端点并根据相应的方法发出请求。
例如:- 如果我们的用户在 /user/register 处获取,我们的服务器将对我们的注册方法发出POST请求,我们将在下一部分中编写。
如果您已经阅读到此博客的这一步,请拍拍自己的肩膀,因为您已经创建了自己的服务器,设置了自己的环境变量,创建、配置并将自己的 postgreSQL 数据库连接到服务器,并且已经完成了授权用户访问您的 Web 应用程序的第一步。
从这里开始它会变得更加有趣,您将编写可以在现实世界项目上运行的函数,并且您会爱上它。
将用户注册到我们的应用程序
在我们的控制器目录中,我们将创建register.js,在其中添加函数来注册我们的用户。
这里我们将编写 SQL 查询来防止 SQL 注入。感兴趣的朋友可以点击此处了解更多关于 SQL 注入的知识。我们将在每个步骤中进行错误处理,并使用 try-catch 方法编写函数。
在继续执行我们的函数之前,我们需要更新.env文件。
我们将在.env文件中添加以下变量
SECRET_KEY = any random string here
在register.js中我们将添加以下代码
const bcrypt = require("bcrypt");
const client = require("../configs/database");
const jwt = require("jsonwebtoken");
//Registration Function
exports.register = async (req, res) => {
const { name, email, phonenumber, password } = req.body;
try {
const data = await client.query(`SELECT * FROM users WHERE email= $1;`, [email]); //Checking if user already exists
const arr = data.rows;
if (arr.length != 0) {
return res.status(400).json({
error: "Email already there, No need to register again.",
});
}
else {
bcrypt.hash(password, 10, (err, hash) => {
if (err)
res.status(err).json({
error: "Server error",
});
const user = {
name,
email,
phonenumber,
password: hash,
};
var flag = 1; //Declaring a flag
//Inserting data into the database
client
.query(`INSERT INTO users (name, email, phonenumber, password) VALUES ($1,$2,$3,$4);`, [user.name, user.email, user.phonenumber, user.password], (err) => {
if (err) {
flag = 0; //If user is not inserted is not inserted to database assigning flag as 0/false.
console.error(err);
return res.status(500).json({
error: "Database error"
})
}
else {
flag = 1;
res.status(200).send({ message: 'User added to database, not verified' });
}
})
if (flag) {
const token = jwt.sign( //Signing a jwt token
{
email: user.email
},
process.env.SECRET_KEY
);
};
});
}
}
catch (err) {
console.log(err);
res.status(500).json({
error: "Database error while registring user!", //Database connection error
});
};
}
此代码将在您的数据库中注册您的用户,我们可以在 VS 代码上的 Postman 或 ThunderClient 扩展上测试此端点。
我们将在下一节中理解此代码。
了解注册功能
为了理解我们的注册功能,我们将其分成不同的块。
第 1 步:检查用户是否已存在于我们的数据库中
const data = await client.query(`SELECT * FROM users WHERE email= $1;`, [email]); //Checking if user already exists
const arr = data.rows;
if (arr.length != 0) {
return res.status(400).json({
error: "Email already there, No need to register again.",
});
}
...
这里我们查询客户端,也就是数据库,检查用户邮箱地址是否已经存在于数据库中。然后,我们检查查询结果行的长度(如果邮箱地址存在)。
如果长度为 0,我们会向用户发出错误响应,指出他不需要再次注册。
第 2 块:散列用户密码
bcrypt.hash(password, 10, (err, hash) => {
if (err)
res.status(err).json({
error: "Server error",
});
const user = {
name,
email,
phonenumber,
password: hash,
};
...
这里我们使用 bcrypt 来哈希用户密码,这样即使任何第三方获取了我们的数据库,我们的用户密码也是安全的,无法被破解。函数中的第 10 个参数表示该函数存储密码时执行的加盐轮数。您可以点击此处
了解更多关于 bcrypt 的信息。
然后我们创建一个用户对象来存储用户的所有输入值和散列密码。
第 3 部分:将用户信息插入数据库
var flag = 1; //Declaring a flag
//Inserting data into the database
client
.query(`INSERT INTO users (name, email, phonenumber, password) VALUES ($1,$2,$3,$4);`, [user.name, user.email, user.phonenumber, user.password], (err) => {
if (err) {
flag = 0; //If user is not inserted is not inserted to database assigning flag as 0/false.
console.error(err);
return res.status(500).json({
error: "Database error"
})
}
else {
flag = 1;
res.status(200).send({ message: 'User added to database' });
}
})
...
这里我们查询数据库,并将哈希密码插入到用户数据中。此外,我们还声明了一个名为 flag 的变量,它将在后续部分中充当布尔值。
第 4 步:为每个用户签名 JSON Web Token
if (flag) {
const token = jwt.sign( //Signing a jwt token
{
email: user.email
},
process.env.SECRET_KEY
);
};
...
如果用户已注册到我们的数据库(布尔标志可检查这一点),我们会为用户签名一个 Json Web Token。Json Web Token 提供签名并加密传递给它的数据。在本例中,我们会加密用户的邮箱地址,以便用户登录应用时进行身份识别。process.env.SECRET_KEY是我们
.env文件中的环境变量,它提供一个随机字符串,并使用 JWT 函数加密我们的数据。
如果您有兴趣,可以从这里了解有关 jwt 的更多信息,或者从这里参考其包文档
现在我们已经完成了用户注册,我们可以通过在 /users/register 上发出 POST 请求并输入所需数据来检查这些端点。
太棒了!!🥳🥳🥳
到目前为止,您已经开发了自己的后端来注册用户,并且遵循了最佳实践。
您已经阻止了 SQL 注入,进行了良好的错误处理,并将用户数据安全地存储在数据库中。现在我们将了解如何将用户登录到我们的应用程序。
将用户登录到我们的应用程序
在我们的控制器目录中,我们将创建login.js,在其中添加用于登录用户的功能。
在这里,我们将编写 SQL 查询来防止 SQL 注入。感兴趣的朋友可以点击此处了解更多关于 SQL 注入的知识。我们将在每个步骤中进行错误处理,并使用 try-catch 方法编写函数。
我们将以下代码添加到您的login.js文件中
const bcrypt = require("bcrypt");
const client = require("../configs/database");
const jwt = require("jsonwebtoken");
//Login Function
exports.login = async (req, res) => {
const { email, password } = req.body;
try {
const data = await client.query(`SELECT * FROM users WHERE email= $1;`, [email]) //Verifying if the user exists in the database
const user = data.rows;
if (user.length === 0) {
res.status(400).json({
error: "User is not registered, Sign Up first",
});
}
else {
bcrypt.compare(password, user[0].password, (err, result) => { //Comparing the hashed password
if (err) {
res.status(500).json({
error: "Server error",
});
} else if (result === true) { //Checking if credentials match
const token = jwt.sign(
{
email: email,
},
process.env.SECRET_KEY
);
res.status(200).json({
message: "User signed in!",
token: token,
});
}
else {
//Declaring the errors
if (result != true)
res.status(400).json({
error: "Enter correct password!",
});
}
})
}
} catch (err) {
console.log(err);
res.status(500).json({
error: "Database error occurred while signing in!", //Database connection error
});
};
};
此代码将在您的数据库中登录您的用户,我们可以在 VS 代码上的 Postman 或 ThunderClient 扩展上测试此端点。
我们将在下一节中理解此代码。
了解登录功能
为了理解我们的登录功能,我们将其分成不同的块。
第 1 步:检查用户是否已注册我们的应用程序
const { email, password } = req.body;
try {
const data = await client.query(`SELECT * FROM users WHERE email= $1;`, [email]) //Verifying if the user exists in the database
const user = data.rows;
if (user.length === 0) {
res.status(400).json({
error: "User is not registered, Sign Up first",
});
}
...
这里我们查询数据库,检查用户输入的值是否存在于数据库中。如果查询的响应长度为0,即没有用户拥有这些凭据,则会抛出错误。
第 2 部分:将散列密码与用户密码进行比较
else {
bcrypt.compare(password, user[0].password, (err, result) => { //Comparing the hashed password
if (err) {
res.status(500).json({
error: "Server error",
});
} else if (result === true) { //Checking if credentials match
const token = jwt.sign(
{
email: email,
},
process.env.SECRET_KEY
);
res.status(200).json({
message: "User signed in!",
token: token,
});
}
...
这里,如果用户存在于我们的数据库中,我们将使用bcrypt 的比较方法来检查用户输入的密码和数据库中的用户密码是否相等。
如果这两个密码相同,我们会为用户签署一个 JWT 令牌,并在其中加密用户的电子邮件。
第 3 部分:处理用户登录时的错误
else {
//Declaring the errors
if (result != true)
res.status(400).json({
error: "Enter correct password!",
});
}
})
}
} catch (err) {
console.log(err);
res.status(500).json({
error: "Database error occurred while signing in!", //Database connection error
});
};
在这部分代码中,我们会告诉用户在登录应用程序时是否存在任何错误,无论是与他的凭据有关还是与数据库有关。
现在,我们已经完成了用户登录,我们可以通过在 /users/loigin 上发出 POST 请求并输入所需数据来检查这些端点。
如果一切顺利,就会出现响应 200 OK,并且您已成功授权和验证用户,而无需自行使用第三方应用程序。
是时候庆祝一下了,因为你做得相当不错,并且掌握了如何使用 Node、Express 和其他技术,并找到了自己的方向。
用户身份验证还有很多内容,这只是你的开始。你已经学到了很多,现在可以自己探索了。
您现在还能做什么?
- 使用 RegEx 验证用户的电子邮件、密码和电话号码。
- 通过使用 NodeMailer 向用户发送邮件来验证用户的电子邮件。
- 防止对您的服务器进行不同的攻击,例如 XSS 攻击。
- 添加更多端点并了解中间件。
这是我的第一篇博客,也是对社区的一点小贡献,希望大家能多多关爱♥
您可以在这里联系我,提出任何反馈和疑问。
文章来源:https://dev.to/shreshthgoyal/user-authorization-in-nodejs-using-postgresql-4gl