使用 Node.js 和 Vue.js 完成登录系统 | RestAPI 和 JWT | 第 [1/2] 部分
第一部分的目标:使用 JWT 和 MySQL 的 RestAPI
1.什么是 RestAPI?
2.安装依赖项
3. 设置数据库
4. 设置 Express Router 并创建路由
5.创建中间件(验证)
6. 注册(/sign-up)路线(使用 JWT)
7. 登录(/login)路由(使用 JWT)
8. 使用登录保护路由
9. 结论
最初发表于webdeasy.de!
在本文中,您将学习如何使用Node.js、JWT(JSON Web Tokens)和MySQL创建自己的RestAPI用于用户身份验证——一个完整的登录系统。
本文是“使用 Node.js 和 Vue.js 构建完整登录系统”系列文章的第一部分。我们将介绍如何使用 Node.js 作为后端,Vue.js 作为前端,实现一个完整的登录系统。本教程分为两部分,因此您不必局限于 Vue.js 前端,但我们在本文中开发的 restAPI 可以应用于其他前端,以及 Angular 或 React。
➡️第 2 部分:使用 Node.js 和 Vue.js | Vuex 完成登录系统
第一部分的目标:使用 JWT 和 MySQL 的 RestAPI
我们创建一个在本地服务器上运行的 Node.js 应用程序。我们有一个MySQL数据库,用于存储用户数据。为了进行身份验证,我们需要查询这些数据,并借助JWT扩展为用户创建会话。
最后,您将获得一个可执行应用程序,并可以在本指南的帮助下将其部署到您自己的服务器上。现在我们终于要开始了!🙂
1.什么是 RestAPI?
RestAPI 代表服务器和客户端之间的接口。通过常规的 HTTP 请求,我们可以到达服务器并执行编程功能,例如使用相应的密码对用户进行身份验证。
由于本教程不适合完全的初学者,我假设你已经对 Node.js 有所了解,因此我们跳过安装部分,直接进入精彩部分。如果不是这样,你可以在这里找到一份不错的 Node.js 入门指南。
2.安装依赖项
现在我们的 Node.js 应用已经准备好安装依赖项了。我们需要以下模块:
我们使用以下 CLI 命令安装这些模块:
npm install bcryptjs body-parser express jsonwebtoken mysql uuid cors
3. 设置数据库
我使用XAMPP作为数据库,因此我可以在本地托管自己的数据库。当然,您也可以使用任何其他(远程)数据库。
为了我们也可以通过我们的 Node.js 应用程序访问此连接,我们创建了自己的类文件,然后将其包含在我们的路由器中。
// lib/db.js
const mysql = require('mysql');
const connection = mysql.createConnection({
host: 'localhost',
user: 'node-jwt',
database: 'node-jwt',
password: '********'
});
connection.connect();
module.exports = connection;
4. 设置 Express Router 并创建路由
我们的入口文件是index.js,包含我们的 Web 服务器的启动以及我们在文件routes/router.js中定义的路由的集成。
// index.js
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const cors = require('cors');
// set up port
const PORT = process.env.PORT || 3000;
app.use(bodyParser.json());
app.use(cors());
// add routes
const router = require('./routes/router.js');
app.use('/api', router);
// run server
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
在router.js中,我们定义路由,然后将逻辑打包到其中。我们在这里使用一个额外的文件是为了清晰起见。如果你的应用程序有 20 条或更多路由,index.js就会变得混乱。这就是我们将路由外包的原因。
// routes/router.js
const express = require('express');
const router = express.Router();
const bcrypt = require('bcryptjs');
const uuid = require('uuid');
const jwt = require('jsonwebtoken');
const db = require('../lib/db.js');
const userMiddleware = require('../middleware/users.js');
router.post('/sign-up', (req, res, next) => {});
router.post('/login', (req, res, next) => {});
router.get('/secret-route', (req, res, next) => {
res.send('This is the secret content. Only logged in users can see that!');
});
module.exports = router;
这里我们注册了路由/api/sign-up用于注册,以及/api/login用于登录。我们还有一个路由/secret-route,只有登录后才能调用。目前每个用户都可以访问它。稍后会详细介绍。
在第 10 行我们还包含了数据库连接的文件。
我们还添加了文件../middleware/users.js,其中包含用于验证请求的代码。这意味着我们会检查用户是否输入了密码,以及用户名是否符合规范。之后,我们会将这些查询作为中间件切换到路由调用中。
5.创建中间件(验证)
中间件是一个在两个组件之间切换的小程序。在本例中,在请求和实际注册之间,我们有一个中间件来验证输入的数据。对于注册,验证可以如下所示:
// middleware/users.js
module.exports = {
validateRegister: (req, res, next) => {
// username min length 3
if (!req.body.username || req.body.username.length < 3) {
return res.status(400).send({
msg: 'Please enter a username with min. 3 chars'
});
}
// password min 6 chars
if (!req.body.password || req.body.password.length < 6) {
return res.status(400).send({
msg: 'Please enter a password with min. 6 chars'
});
}
// password (repeat) does not match
if (
!req.body.password_repeat ||
req.body.password != req.body.password_repeat
) {
return res.status(400).send({
msg: 'Both passwords must match'
});
}
next();
}
};
当调用/sign-up路由时,我们的中间件应该被执行。为此,请将标记的行修改如下:
// routes/router.js
const express = require('express');
const router = express.Router();
const userMiddleware = require('../middleware/users.js');
router.post('sign-up', userMiddleware.validateRegister, (req, res, next) => {});
router.post('login', (req, res, next) => {});
module.exports = router;
6. 注册(/sign-up)路线(使用 JWT)
要将新用户添加到数据库,我们必须检查用户名是否不存在。如果用户已存在,则会发出错误消息。如果用户尚不存在,则使用我们的模块bcrypt对输入的密码进行哈希(加密),然后将所有数据输入数据库。
// routes/router.js
router.post('/sign-up', userMiddleware.validateRegister, (req, res, next) => {
db.query(
`SELECT * FROM users WHERE LOWER(username) = LOWER(${db.escape(
req.body.username
)});`,
(err, result) => {
if (result.length) {
return res.status(409).send({
msg: 'This username is already in use!'
});
} else {
// username is available
bcrypt.hash(req.body.password, 10, (err, hash) => {
if (err) {
return res.status(500).send({
msg: err
});
} else {
// has hashed pw => add to database
db.query(
`INSERT INTO users (id, username, password, registered) VALUES ('${uuid.v4()}', ${db.escape(
req.body.username
)}, ${db.escape(hash)}, now())`,
(err, result) => {
if (err) {
throw err;
return res.status(400).send({
msg: err
});
}
return res.status(201).send({
msg: 'Registered!'
});
}
);
}
});
}
}
);
});
重要的是函数db.escape()
,例如第 23 行。它屏蔽了传递的参数,以避免 SQL 注入。如果用户输入成功,则返回状态码 201(“已创建”),并终止函数调用。
7. 登录(/login)路由(使用 JWT)
除了注册流程外,我们还为已注册用户提供了一个登录路径。在这里,您可以按用户名搜索相应的数据库条目。然后,借助 ,从数据库中检查输入的密码jwt.compare()
。第 44 行中的一条简短的 SQL 查询将上次登录日期/时间设置为当前值。
// routes/router.js
router.post('/login', (req, res, next) => {
db.query(
`SELECT * FROM users WHERE username = ${db.escape(req.body.username)};`,
(err, result) => {
// user does not exists
if (err) {
throw err;
return res.status(400).send({
msg: err
});
}
if (!result.length) {
return res.status(401).send({
msg: 'Username or password is incorrect!'
});
}
// check password
bcrypt.compare(
req.body.password,
result[0]['password'],
(bErr, bResult) => {
// wrong password
if (bErr) {
throw bErr;
return res.status(401).send({
msg: 'Username or password is incorrect!'
});
}
if (bResult) {
const token = jwt.sign({
username: result[0].username,
userId: result[0].id
},
'SECRETKEY', {
expiresIn: '7d'
}
);
db.query(
`UPDATE users SET last_login = now() WHERE id = '${result[0].id}'`
);
return res.status(200).send({
msg: 'Logged in!',
token,
user: result[0]
});
}
return res.status(401).send({
msg: 'Username or password is incorrect!'
});
}
);
}
);
});
在第 36 行和第 37 行,我们传递了想要“存储”在 JWT 令牌中的变量。这样我们就可以在受保护的路由中访问这些变量。
在第 39 行,你必须传递一个用于生成 JWT 令牌的密钥,这对于后续的验证至关重要。你可以在此处输入任何字符串。
您还可以在第 40 行指定令牌的有效期。“1h”或“3m”等值在此处有效。您还可以在文档中阅读有关各个值和参数的信息。
如果密码错误或用户名不存在,则会显示一条错误消息。此消息是故意设计的,因为潜在的攻击者可能会获取有关单个用户配置文件是否存在的信息。
如果登录成功,则会返回用户对象和由 JWT 生成的令牌。此令牌对于所有需要登录的路由都至关重要。在第二部分(Vue.js 前端)中,您将学习如何在每次请求中传递此令牌。如果您使用Postman测试 RestAPI ,则可以按照以下语法“Bearer KEY”指定以“Authorization”为键值的令牌。
8. 使用登录保护路由
最重要的路线现已准备就绪。我们可以添加新用户并使用现有帐户登录。现在我们需要保护路线。这意味着只有注册用户才能访问它们。
因此,我们在users.js中创建一个新的中间件。令牌从请求的标头中获取,并通过 JWT 进行验证。
// middleware/users.js
isLoggedIn: (req, res, next) => {
try {
const token = req.headers.authorization.split(' ')[1];
const decoded = jwt.verify(
token,
'SECRETKEY'
);
req.userData = decoded;
next();
} catch (err) {
return res.status(401).send({
msg: 'Your session is not valid!'
});
}
}
在标记的行中,您必须输入已经生成 JWT 的相同密钥。
要立即保护路由,只需在调用路由时包含此中间件,如下所示:
// routes/router.js
router.get('/secret-route', userMiddleware.isLoggedIn, (req, res, next) => {
console.log(req.userData);
res.send('This is the secret content. Only logged in users can see that!');
});
包含req.userData
我们存储在 JWT 密钥中的数据(在本例中username
为 和)。例如,userId
这允许我们使用 ,从数据库中读取用户定义的值,用于受保护的路由。userId
9. 结论
➡️继续第 2 部分:使用 Node.js 和 Vue.js 完成登录系统 | Vuex | 第 [2/2] 部分
就这样!在第一部分中,我们已经为我们的应用程序编写了一个完整的 RestAPI,用于验证和会话处理。您可以将此系统用于您的前端(无论是 Angular、React 还是 Vue)。
感谢阅读!如果你喜欢这篇文章,请告诉我并分享!如果你感兴趣,可以看看我的博客,并在推特上关注我! 😊
文章来源:https://dev.to/webdeasy/complete-login-system-with-node-js-vue-js-restapi-jwt-part-1-2-1fm6