使用 PostgreSQL 在 NodeJS 中进行用户授权

2025-06-07

使用 PostgreSQL 在 NodeJS 中进行用户授权

用户身份验证是开发 Web 应用程序时不可或缺的部分之一。虽然我们可以使用FirebasePassport等第三方应用程序来验证用户身份,但在本博客中,我们将使用一种简单且自主开发的方法,即使用 NodeJS 和 PostgreSQL 创建 REST API,这将帮助我们理解身份验证的基础知识。

先决条件和使用的技术

  • nodeJS
  • nodemon
  • 表达
  • 科尔斯
  • 前列腺素
  • 加密
  • jwt
  • dotenv

另外最好在ElephantSQL上拥有一个帐户。


基本结构

设置好所有依赖项后,您可以先创建一个文件夹结构。这里我们将使用 MVC 文件夹结构,即模型 --> 视图 --> 控制器的结构。我们将为每个任务创建不同的文件夹。

在根目录中运行以下命令

touch server.js
mkdir routes controller routes configs
Enter fullscreen mode Exit fullscreen mode

这将创建我们需要授权用户的所有文件夹。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}.`);

})
Enter fullscreen mode Exit fullscreen mode

这里我们添加了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,

);
Enter fullscreen mode Exit fullscreen mode

自行注册的用户应拥有唯一的电子邮件和唯一的电话号码,以防止同一用户多次注册以及用户机器人的垃圾邮件。

现在我们将拥有用户表,可以在其中存储用户信息。

使用我们的 Node 应用程序

现在我们已经构建了数据库,我们需要配置它并将其连接到服务器。在项目目录中,我们将直接进入configs目录并创建两个文件database.jsdotenv.js

在添加我们的 Postgres 凭证之前,我们将在根目录中创建一个新文件 .env,将所有值存储为环境变量,这样如果任何其他人获得我们的代码,他们将无法访问我们的凭证。

在我们的 .env 中,添加以下几行

DB_URL = paste your database URL here
Enter fullscreen mode Exit fullscreen mode

现在在我们的dotenv.js中我们将添加以下几行

if (process.env.NODE_ENV !== 'production') {

require('dotenv').config() //Configuring dotenv during development stage

}
Enter fullscreen mode Exit fullscreen mode

这意味着如果我们处于开发阶段,服务器必须配置我们的 dotenv 文件。

现在要将我们的数据库连接到后端,我们必须在database.js中添加这些行来配置我们的 postgres 数据库。

const { Client } = require("pg");

const client = new Client(process.env.DB_URL); //Configuring PostgresSQL Database

module.exports = client;
Enter fullscreen mode Exit fullscreen mode

现在我们已经配置了数据库,为了将其连接到我们的数据库,我们必须将这些行添加到我们的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!");}

});
Enter fullscreen mode Exit fullscreen mode

通过我们的控制台,我们将知道我们是否连接到本地主机上的数据库。

创建并路由我们的用户端点

在我们的server.js中,我们将添加以下代码行,只是为了告诉服务器,每当我们获取 /users 时,它必须运行我们的用户方法。

const  user  =  require("./routes/user");

app.use("/user",  user);  //Route for /user endpoint of API
Enter fullscreen mode Exit fullscreen mode

在这里我们可以为不同的任务创建多个端点。

我们将在下一节中了解什么是 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;
Enter fullscreen mode Exit fullscreen mode

这里我们告诉服务器转到这些端点并根据相应的方法发出请求。

例如:- 如果我们的用户在 /user/register 处获取,我们的服务器将对我们的注册方法发出POST请求,我们将在下一部分中编写。

如果您已经阅读到此博客的这一步,请拍拍自己的肩膀,因为您已经创建了自己的服务器,设置了自己的环境变量,创建、配置并将自己的 postgreSQL 数据库连接到服务器,并且已经完成了授权用户访问您的 Web 应用程序的第一步。

从这里开始它会变得更加有趣,您将编写可以在现实世界项目上运行的函数,并且您会爱上它。

将用户注册到我们的应用程序

在我们的控制器目录中,我们将创建register.js,在其中添加函数来注册我们的用户。

这里我们将编写 SQL 查询来防止 SQL 注入。感兴趣的朋友可以点击此处了解更多关于 SQL 注入的知识。我们在每个步骤中进行错误处理,并使用 try-catch 方法编写函数。

在继续执行我们的函数之前,我们需要更新.env文件。
我们将在.env文件中添加以下变量

SECRET_KEY = any random string here

Enter fullscreen mode Exit fullscreen mode

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
});
};
}
Enter fullscreen mode Exit fullscreen mode

此代码将在您的数据库中注册您的用户,我们可以在 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.",
});
}
...
Enter fullscreen mode Exit fullscreen mode

这里我们查询客户端,也就是数据库,检查用户邮箱地址是否已经存在于数据库中。然后,我们检查查询结果行的长度(如果邮箱地址存在)。

如果长度为 0,我们会向用户发出错误响应,指出他不需要再次注册。

第 2 块:散列用户密码

bcrypt.hash(password, 10, (err, hash) => {
if (err)
res.status(err).json({
error: "Server error",
});
const  user  = {
name,
email,
phonenumber,
password: hash,
};

...
Enter fullscreen mode Exit fullscreen mode

这里我们使用 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' });
}
})

...
Enter fullscreen mode Exit fullscreen mode

这里我们查询数据库,并将哈希密码插入到用户数据中。此外,我们还声明了一个名为 flag 的变量,它将在后续部分中充当布尔值。

第 4 步:为每个用户签名 JSON Web Token

if (flag) {
const  token  = jwt.sign( //Signing a jwt token
{
email: user.email
},
process.env.SECRET_KEY
);
};

...
Enter fullscreen mode Exit fullscreen mode

如果用户已注册到我们的数据库(布尔标志可检查这一点),我们会为用户签名一个 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
});
};
};
Enter fullscreen mode Exit fullscreen mode

此代码将在您的数据库中登录您的用户,我们可以在 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",
});
}

...
Enter fullscreen mode Exit fullscreen mode

这里我们查询数据库,检查用户输入的值是否存在于数据库中。如果查询的响应长度为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,
});
}

...
Enter fullscreen mode Exit fullscreen mode

这里,如果用户存在于我们的数据库中,我们将使用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
});
};
Enter fullscreen mode Exit fullscreen mode

在这部分代码中,我们会告诉用户在登录应用程序时是否存在任何错误,无论是与他的凭据有关还是与数据库有关。

现在,我们已经完成了用户登录,我们可以通过在 /users/loigin 上发出 POST 请求并输入所需数据来检查这些端点。

如果一切顺利,就会出现响应 200 OK,并且您已成功授权和验证用户,而无需自行使用第三方应用程序。

是时候庆祝一下了,因为你做得相当不错,并且掌握了如何使用 Node、Express 和其他技术,并找到了自己的方向。
用户身份验证还有很多内容,这只是你的开始。你已经学到了很多,现在可以自己探索了。


您现在还能做什么?

  • 使用 RegEx 验证用户的电子邮件、密码和电话号码。
  • 通过使用 NodeMailer 向用户发送邮件来验证用户的电子邮件。
  • 防止对您的服务器进行不同的攻击,例如 XSS 攻击。
  • 添加更多端点并了解中间件。

这是我的第一篇博客,也是对社区的一点小贡献,希望大家能多多关爱♥

您可以在这里联系我,提出任何反馈和疑问。

文章来源:https://dev.to/shreshthgoyal/user-authorization-in-nodejs-using-postgresql-4gl
PREV
ES6 手册:你需要知道的一切
NEXT
树上看护新手指南