JWT
(JSON Web Tokens
) 是一种流行的身份验证方法,可让我们以 JSON
对象的形式在各方之间安全地传输信息。在本文中,我们将介绍如何在 Node.js
应用程序中 使用JWT
身份验证。
JWT
是一个紧凑且独立的 JSON
对象,其中包含有关用户身份验证的信息。该信息使用密钥进行数字签名,可以被验证和信任。
JWT
由以下几方面组成:
header
(标头):包含有关 JWT
编码方式的信息,例如用于签署令牌的算法。payload
(有效负载):这包含声明。声明是关于实体(通常是用户)和附加元数据的声明。声明分为三种类型:注册声明、公共声明和私人声明。signature
(签名):这用于验证 JWT
的发送者是否是其声称的身份,并确保消息在此发送过程中没有被更改。签名是通过获取编码标头、编码有效负载和密钥,然后使用指定算法对它们进行创建的。以下是一个 JWT
示例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
在我们讲解之前,让我们设置我们的开发环境。我们将使用以下工具和包:
Node.js
:我们将使用 Node.js
作为服务器端语言。Express
:这是一个流行的 Node.js Web
框架,我们将用它来处理 HTTP
请求。jsonwebtoken
:这是一个流行的库,用于在 Node.js
中生成和验证 JWT
。让我们首先使用以下命令创建一个新的 Node.js
项目:
npm init
接下来,运行以下命令来安装所需的软件包:
npm install express jsonwebtoken
首先,让我们创建一个名为index.js
的新文件,并添加以下代码来设置我们的 Express
应用程序:
const express = require('express');
const app = express();
const port = 3000;
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});
接下来,让我们创建一个简单的路由,如果用户通过身份验证,它将返回一条消息:
app.get('/protected', (req, res) => {
res.send('Welcome to the protected route');
});
通过添加中间件来实现身份验证过程,该中间件将检查传入请求标头中是否存在有效的 JWT
:
const jwt = require('jsonwebtoken');
const secretKey = 'secret_key';
const authenticate = (req, res, next) => {
const token = req.headers['authorization'];
if (!token) {
return res.status(401).send('Access Denied. No token provided.');
}
try {
const decoded = jwt.verify(token, secretKey);
req.user = decoded;
next();
} catch (error) {
return res.status(400).send('Invalid Token.');
}
};
app.get('/protected', authenticate, (req, res) => {
res.send('Welcome to the protected route');
});
在上面的代码中,我们导入了jsonwebtoken
库并创建了用于签名和验证 JWT
的密钥。然后,我们创建了一个名为 authenticate
的中间件函数,该函数将用于检查传入请求标头Authorization
中的有效 JWT
。
如果存在令牌,我们将使用jsonwebtoken
库中的verify
方法来解码令牌并检查其有效性。如果令牌有效,我们将解码后的信息添加到req
对象中,并调用next
函数以继续下一个中间件或路由处理程序。
如果未提供令牌或令牌无效,我们将分别返回带有401
或400
状态码的错误消息。
现在我们已经完成了身份验证过程,让我们创建一个用于生成 JWT
的路由。例如,这可以在用户登录时完成。
app.post('/login', (req, res) => {
const user = {
id: 1,
username: 'leo'
};
const token = jwt.sign({ user }, secretKey, { expiresIn: '1h' });
res.header('Authorization', token).send(user);
});
在上面的代码中,我们创建了一个/login
路由,当收到POST
请求时,该路由将生成 JWT
。我们创建了一个模拟用户对象,并使用jsonwebtoken
库中的sign方法生成 JWT
。我们指定了密钥并将expiresIn
选项设置为1h
,这意味着令牌将在一小时后过期。
最后,我们将生成的 JWT
添加到响应标头的Authorization
字段中,并将用户信息发送回客户端。
JWT
的一个问题是它们的寿命很短,需要定期刷新。为了解决这个问题,我们可以使用刷新令牌。
刷新令牌是与访问令牌是不同的,它们可用于生成新的访问令牌。出于安全原因,这使我们能够保持访问令牌的短暂性,同时仍然允许用户在更长的时间内保持身份验证。
我们修改/login
路由以生成访问令牌和刷新令牌:
app.post('/login', (req, res) => {
const user = {
id: 1,
username: 'leo'
};
const accessToken = jwt.sign({ user }, secretKey, { expiresIn: '1h' });
const refreshToken = jwt.sign({ user }, secretKey, { expiresIn: '1d' });
res.cookie('refreshToken', refreshToken, { httpOnly: true, sameSite: 'strict' })
.header('Authorization', accessToken)
.send(user);
});
接下来,我们将创建一条用于刷新访问令牌的新路由。该路由将从客户端接收刷新令牌并使用它生成新的访问令牌:
app.post('/refresh', (req, res) => {
const refreshToken = req.cookies['refreshToken'];
if (!refreshToken) {
return res.status(401).send('Access Denied. No refresh token provided.');
}
try {
const decoded = jwt.verify(refreshToken, secretKey);
const accessToken = jwt.sign({ user: decoded.user }, secretKey, { expiresIn: '1h' });
res
.header('Authorization', accessToken)
.send(decoded.user);
} catch (error) {
return res.status(400).send('Invalid refresh token.');
}
});
在上面的代码中,我们创建了一个用于刷新访问令牌的新路由。该路由检查 cookie
中是否存在刷新令牌,如果找到令牌,我们将使用verify
方法检查其有效性。如果刷新令牌有效,我们将生成一个与原始令牌具有相同信息的新访问令牌,并将其发送回客户端。
最后,我们可以更新authenticate
中间件以检查访问令牌和刷新令牌,并在必要时刷新访问令牌:
const authenticate = (req, res, next) => {
const accessToken = req.headers['authorization'];
const refreshToken = req.cookies['refreshToken'];
if (!accessToken && !refreshToken) {
return res.status(401).send('Access Denied. No token provided.');
}
try {
const decoded = jwt.verify(accessToken, secretKey);
req.user = decoded.user;
next();
} catch (error) {
if (!refreshToken) {
return res.status(401).send('Access Denied. No refresh token provided.');
}
try {
const decoded = jwt.verify(refreshToken, secretKey);
const accessToken = jwt.sign({ user: decoded.user }, secretKey, { expiresIn: '1h' });
res
.cookie('refreshToken', refreshToken, { httpOnly: true, sameSite: 'strict' })
.header('Authorization', accessToken)
.send(decoded.user);
} catch (error) {
return res.status(400).send('Invalid Token.');
}
}
};
在上面的代码中,我们首先检查访问令牌和刷新令牌是否存在。如果仅提供了访问令牌,我们会检查其有效性,如果有效则继续请求。如果访问令牌无效,我们将检查是否存在刷新令牌。如果找到刷新令牌,我们将验证其有效性并根据刷新令牌中的信息生成新的访问令牌。如果刷新令牌无效,我们会向客户端发送一条错误消息。
完成这些更改后,我们现在拥有一个带有刷新令牌机制的完整 JWT
身份验证系统。要使用该系统,客户端可以首先向路由发出请求/login
以接收访问令牌和刷新令牌。然后,他们可以将访问令牌包含在所有后续请求标头中的Authorization
以访问受保护的路由。如果访问令牌过期,客户端可以向路由发出请求/refresh
以接收新的访问令牌。