在现代web
应用中,用户身份认证是非常重要且必不可少的一环。而使用Node.js
和Express
框架,可以方便地实现用户身份认证。而在这个过程中,jsonwebtoken
这个基于JWT
协议的模块可以帮助我们实现安全且可靠的身份认证机制,可以让我们轻松地生成、解析和验证JWT
。本文主要介绍如何在Node.js
的Express
框架中使用jsonwebtoken
模块来实现用户身份认证。
一、什么是jwt
jwt介绍:https://restfulapi.cn/jwt
JWT
(JSON Web Token
)是一种基于JSON
的开放标准,用于在网络上以安全方式传输声明。JWT
通常用于身份验证和授权。它是一种可自包含的身份认证机制,由三部分组成:头部、载荷和签名。JWT
的头部和载荷都是使用Base64
编码后的JSON
字符串,签名是由头部和载荷使用密钥进行哈希计算得到的字符串。JWT
可以在客户端和服务器之间传输,并且可以在传输过程中防止数据被篡改。由于它是无状态的,因此可以简化Web
应用程序的开发和维护。
二、jsonwebtoken基本使用
jsonwebtoken
是一个Node.js
库,用于创建、解码和验证JWT
令牌(JSON Web Tokens
)。在Web
应用中,这些令牌通常用于身份验证和授权。
以下是如何在Node.js
应用中使用jsonwebtoken
的基本步骤。
1. 安装jsonwebtoken
首先,你需要使用npm或yarn将jsonwebtoken包添加到你的项目中:
npm install jsonwebtoken --save
或者
yarn add jsonwebtoken
2. 导入使用
然后,在你的代码中导入jsonwebtoken:
const jwt = require('jsonwebtoken');
3. 创建token
创建一个令牌:
let token = jwt.sign({ foo: 'bar' }, 'shhhhh');
在上面的代码中,我们使用jwt.sign
方法创建了一个新的令牌。
这个方法需要两个参数:
- 一个包含你的
payload
的对象(这个对象中的数据会被编码到生成的令牌中)比如:userInfo
, - 用来签名令牌的秘密字符串。
4. 验证token
验证一个令牌:
jwt.verify(token, 'shhhhh', (err, decoded) => {
if (err) {
// 处理错误
} else {
// 使用解码后的令牌数据
}
});
使用jwt.verify
方法可以解码并验证一个令牌。
该方法需要三个参数:
- 解码的令牌,
- 用于签名的相同的秘密字符串,
- 操作完成时被调用的回调函数。
注意:
-
所有的令牌都应该只通过
HTTPS
发送,以防止被拦截。 -
秘密字符串应该在你的应用中保持私密,不能让其他人知道。
-
任何存储在令牌中的信息都可以被任何拥有秘密的人解码。因此,你永远不应该在令牌中存储敏感信息(如密码或银行账户信息)。
三、在express中应用示例
在实际项目中,我们可以使用jsonwebtoken
模块来实现身份认证。jsonwebtoken
是基于JSON Web Tokens(JWT)
协议的实现模块,可以用于生成、解析和验证JWT
。JWT
是一种基于JSON
的开放标准(RFC 7519)
,用于在网络上安全地传输声明。
以下是一个使用express
和jsonwebtoken
实现身份认证的示例:
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
// 定义一个中间件函数,用于身份认证
function verifyToken(req, res, next) {
// 从请求头中获取token
const token = req.headers['authorization'];
if (!token) {
return res.status(401).send({
auth: false,
message: 'No token provided.'
});
}
// 验证token是否有效
jwt.verify(token, process.env.SECRET_KEY, (err, decoded) => {
if (err) {
return res.status(500).send({
auth: false,
message: 'Failed to authenticate token.'
});
}
// 将解码的用户信息存储到请求对象中
req.userId = decoded.id;
next();
});
}
// 定义一个路由,需要身份认证
app.get('/api/protected', verifyToken, (req, res, next) => {
// 返回受保护的数据
res.status(200).send({
message: 'Access granted.'
});
});
// 定义一个路由,用于登录,返回token
app.post('/api/login', (req, res, next) => {
// 从数据库中获取用户信息
const user = { id: 123 };
// 生成token
const token = jwt.sign({ id: user.id }, process.env.SECRET_KEY, {
expiresIn: 60 * 60 // token有效期为1小时
});
// 返回token
res.status(200).send({
auth: true,
token: token
});
});
// 启动服务器,监听端口
app.listen(3000, () => {
console.log('Server started on port 3000');
});
在上述示例中,我们定义了一个中间件函数verifyToken
用于身份认证。该函数从请求头中获取token
,并通过jsonwebtoken的verify
方法验证token
是否有效。如果验证通过,则将用户信息存储到请求对象中,以便后续路由函数使用。
我们还定义了两个路由函数,/api/protected
和/api/login
。/api/protected
需要身份认证才能访问,而/api/login
用于登录,并返回生成的token
。
在实际项目中,我们应该将SECRET_KEY
等敏感信息放在环境变量中,以提高安全性。此外,我们还可以使用jsonwebtoken
提供的其他功能,如验证token
的签名算法、payload
和header
等信息。
四、jwt逻辑抽离
在上面的示例中我们用jwt实现了身份认证,但是这只是在一个接口中认证,如果接口多了呢,每次都要将这个认证的中间件加上吗?这样先不说开发实现的累不累,冗余不,单从维护层面来讲,也是很不合理。所以我们应该将认证的逻辑抽离出来,以便于接口调用和维护。具体步骤如下:
1. 新建jwt.js文件
这个文件中就封装三个函数
- token的生成
- token的认证
- 排除哪些接口不需要认证就可以访问,比如
/login
,
// jwt.js
const jwt = require("jsonwebtoken");
const { promisify } = require("util");
const { uuid } = require("../config/config.default");
const tojwt = promisify(jwt.sign);
const verfiy = promisify(jwt.verify);
// 生成token
module.exports.createToken = async (userinfo) => {
var token = await tojwt({ userinfo }, uuid, {
expiresIn: 60 * 60 * 24,
});
return token;
};
// jwt认证的中间件
const jwtAuthMiddleware = async (req, res, next) => {
var token = req.headers.authorization;
token = token ? token.split("Bearer ")[1] : null;
if (!token) {
return res.status(402).json({ error: "请传入token" });
}
if (token) {
try {
let userinfo = await verfiy(token, uuid);
req.user = userinfo;
next();
} catch (error) {
res.status("402").json({ error: "无效的token" });
}
} else {
next();
}
};
// 承认的url排除列表
const jwtAuthExcluedList = ['/api/login', '/api/register'];
// 检查排除列表的中间件
module.exports.jwtAuthExclued = (req, res, next) => {
// 检查请求 URL 是否在排除 jwtAuth 的列表里面
if (jwtAuthExcluedList.includes(req.path)){
next(); // 在列表里,跳过后续中间件
} else {
jwtAuthMiddleware(req, res, next); // 不在列表里,就调用jwt中间件进行身份认证
}
};
这样封装以后,我们只需要在访问路由之前,添加使用jwtAuthExclued
的中间件就可以实现对接口的token
认证,在login
接口中调用createToken
生成token
可以了。
2. 在登录接口中生成token
比如:
- loginController
// login
// 用户登录
exports.login = async (req, res) => {
// 客户端数据验证
// 链接数据库查询
var dbBack = await User.findOne(req.body)
if (!dbBack) {
return res.status(402).json({ error: "邮箱或者密码不正确" })
}
dbBack = dbBack.toJSON()
dbBack.token = await createToken(dbBack)
res.status(200).json(dbBack)
}
3. 添加全局认证中间件
- app.js
const express = require("express");
const app = express();
const { jwtAuthExclued } = require("./util/jwt");
const router = require("./router");
// 添加排除jwt中间件
app.use(jwtAuthExclued);
// 添加路由中间件
app.use('/api', router);
五、jwt和session对比
下面是JWT
和Session
的对比表格:
对比因素 | JWT | Session |
---|---|---|
存储 | 存储在客户端,不需要服务器保持会话状态。 | 存储在服务器,需要服务器维护会话信息。 |
安全性 | 加密较严密,但如果token被窃取,攻击者可以任意使用。 | 如果sessionID被窃取,攻击者可以冒充用户登陆。 |
性能 | 在每次请求时需要验证和解码token,性能较差。 | 只需查找sessionID就能获取会话信息,性能较好。 |
扩展性 | 在多服务器或者跨域环境中更易扩展。 | 在多服务器环境中需要同步session,扩展性较差。 |
数据大小 | JWT的大小比sessionID大,因此需要更多的带宽。 | sessionID大小稳定,对带宽需求较小。 |
到期时间 | 可以为每个token设置不同的过期时间。 | 所有session的过期时间通常相同。 |
客户端存储位置 | 可以存储在Cookie, LocalStorage, SessionStorage中 | 存储在Cookie中。 |
跨域问题 | 无跨域问题,且对于移动应用而言友好。 | 跨域问题复杂,需要服务器支持CORS。 |
状态 | 无状态,服务器不需要保存用户信息。 | 有状态,服务器需要保存用户信息。 |
使用场景 | 用于认证和信息交换,尤其适合单页应用(SPA)和前后端分离的项目 | 主要用于记录用户状态,适配传统的后端渲染的Web服务 |
总体上来说,JWT
适用于前后端分离的API
服务,它可以简化服务端的存储需求,并提供更好的跨平台兼容性和可扩展性,同时也能提供安全可靠的身份认证机制。而Session
则适用于服务器渲染的Web应用,它可以提供更高的安全性和可调用性,同时也能减轻客户端的流量负担。不同的场景需要选择最适合的身份认证方案,根据实际需求进行选择。
六、总结
在本文中,我们探讨了如何在Node.js
的Express
框架中使用jsonwebtoken
来实现身份认证。我们首先介绍了JWT
协议和jsonwebtoken
模块的基础知识,然后展示了如何在Express
中使用jsonwebtoken
来生成、解析和验证JWT
。通过在实际项目中的举例,我们演示了如何将jsonwebtoken
与路由中间件结合使用,实现基于token
的用户身份认证机制。使用jsonwebtoken
可以为我们的web应用提供更安全和可靠的用户身份认证方案,同时jwt
的优点也是很明显的:无需在服务端保存session
信息,具有可扩展性和跨平台兼容性等特点。通过本文的介绍,相信读者已经能够掌握jsonwebtoken
的基本使用方法和原理,能够在实际项目中使用jsonwebtoken
实现安全可靠的身份认证机制。