各位靓仔靓女,晚上好!我是今天的主讲人,大家可以叫我老码。今天咱们聊聊 Node.js 里面 JWT 认证和授权那些事儿,保证听完之后,你也能像玩积木一样,轻松搭建自己的安全系统。
开场白:JWT 是个啥玩意儿?
想象一下,你是一家豪华酒店的前台,每天要接待各种各样的客人。传统的认证方式,就像你每次都要翻看厚厚的住客登记本,确认身份才能给客人开门。效率低不说,还容易出错。
JWT,就好比酒店给住客发的一张电子房卡,上面记录了住客的身份信息和权限。住客每次进房间,只需要出示这张房卡,酒店就能快速验证身份,不需要每次都查登记本。
简单来说,JWT 就是一个包含身份信息和权限的 JSON 对象,经过加密签名后,变成一个字符串。服务器验证这个字符串的签名,就能确认用户的身份和权限。
JWT 的结构:三段论的爱情故事
JWT 由三部分组成,它们之间用点号(.)连接:
-
Header (头部): 记录 JWT 的类型和使用的加密算法。
{ "alg": "HS256", // 加密算法,这里用的是 HMAC SHA256 "typ": "JWT" // JWT 类型 }
这个头部信息,会被 Base64Url 编码,变成 JWT 的第一部分。
-
Payload (载荷): 存放实际的数据,比如用户的 ID、用户名、权限等。
{ "sub": "1234567890", // Subject,通常是用户 ID "name": "John Doe", // 用户名 "admin": true, // 是否管理员 "iat": 1516239022 // Issued At,签发时间 }
Payload 也会被 Base64Url 编码,变成 JWT 的第二部分。需要注意的是,Payload 里的数据是可解码的,所以不要存放敏感信息,比如密码。
-
Signature (签名): 用于验证 JWT 的有效性,防止篡改。
签名是根据 Header 和 Payload,使用 Header 中指定的加密算法,以及一个密钥(Secret Key)计算出来的。
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )
签名会变成 JWT 的第三部分。
Node.js 实现 JWT 认证:代码说话
现在,我们来用 Node.js 实现一个简单的 JWT 认证系统。我们需要用到 jsonwebtoken
这个库。
-
安装 jsonwebtoken:
npm install jsonwebtoken
-
生成 JWT:
const jwt = require('jsonwebtoken'); // 密钥,一定要保密! const secretKey = 'your_secret_key'; // 用户信息 const user = { id: 123, username: 'testuser', role: 'admin' }; // 生成 JWT const token = jwt.sign(user, secretKey, { expiresIn: '1h' }); // 过期时间设置为 1 小时 console.log(token); // 输出生成的 JWT
这段代码会生成一个 JWT,包含了用户的信息,并且设置了过期时间为 1 小时。
jwt.sign
函数接受三个参数:payload
: 要放入 JWT 中的数据。secretKey
: 用于加密 JWT 的密钥。options
: 可选参数,可以设置 JWT 的过期时间、加密算法等。
-
验证 JWT:
const jwt = require('jsonwebtoken'); // 密钥,和生成 JWT 时用的要一致! const secretKey = 'your_secret_key'; // 假设我们从请求头中获取了 JWT const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTIzLCJ1c2VybmFtZSI6InRlc3R1c2VyIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNjc4ODg2ODQ0LCJleHAiOjE2Nzg4OTA0NDR9.mE1Hj8-wR1-l8L5yV4zX0yqZ6d9-oZ2n3g1K8yXz9-Q'; // 这里替换成你实际的 JWT // 验证 JWT jwt.verify(token, secretKey, (err, decoded) => { if (err) { console.log('JWT 验证失败:', err.message); } else { console.log('JWT 验证成功:', decoded); // decoded 包含 JWT 中的 payload } });
这段代码会验证 JWT 的有效性。
jwt.verify
函数接受三个参数:token
: 要验证的 JWT。secretKey
: 用于解密的密钥。callback
: 回调函数,接收两个参数:err
和decoded
。如果 JWT 验证失败,err
会包含错误信息;如果验证成功,decoded
会包含 JWT 中的 payload。
Node.js 实现 JWT 授权:角色权限控制
光有认证还不够,我们还需要授权,也就是控制用户可以访问哪些资源。 JWT 可以在 Payload 中存储用户的角色信息,然后根据角色来判断用户是否有权限访问某个资源。
-
定义角色和权限:
const roles = { 'admin': ['read', 'write', 'delete'], // 管理员可以读、写、删除 'editor': ['read', 'write'], // 编辑可以读、写 'viewer': ['read'] // 访客只能读 };
-
中间件实现权限控制:
const jwt = require('jsonwebtoken'); const secretKey = 'your_secret_key'; const roles = { 'admin': ['read', 'write', 'delete'], 'editor': ['read', 'write'], 'viewer': ['read'] }; // 权限控制中间件 function authorize(requiredRole) { return (req, res, next) => { const token = req.headers.authorization; // 假设 JWT 放在 Authorization 请求头中 if (!token) { return res.status(401).json({ message: '未提供 JWT' }); } jwt.verify(token, secretKey, (err, decoded) => { if (err) { return res.status(403).json({ message: 'JWT 无效' }); } const userRole = decoded.role; // 从 JWT 中获取用户的角色 if (!roles[userRole]) { return res.status(403).json({ message: '角色不存在' }); } if (!roles[userRole].includes(requiredRole)) { return res.status(403).json({ message: '权限不足' }); } req.user = decoded; // 将用户信息放入 req 对象中,方便后续使用 next(); // 继续执行下一个中间件或路由处理函数 }); }; } // 使用示例(需要安装 express) const express = require('express'); const app = express(); app.get('/admin', authorize('delete'), (req, res) => { res.json({ message: '只有管理员才能访问' }); }); app.get('/editor', authorize('write'), (req, res) => { res.json({ message: '编辑和管理员可以访问' }); }); app.get('/viewer', authorize('read'), (req, res) => { res.json({ message: '所有人都可以访问' }); }); app.listen(3000, () => { console.log('Server is running on port 3000'); });
这段代码定义了一个
authorize
中间件,它接收一个requiredRole
参数,表示访问该路由需要的权限。中间件会验证 JWT,然后判断用户的角色是否拥有该权限。如果用户没有权限,会返回 403 错误。
JWT 的优缺点:硬币的两面
-
优点:
- 无状态: 服务器不需要保存用户的会话信息,降低了服务器的压力。
- 可扩展性: 由于 JWT 是自包含的,所以可以很容易地在多个服务之间共享用户信息。
- 跨域认证: JWT 可以很容易地用于跨域认证,比如单点登录(SSO)。
- 移动友好: JWT 可以方便地在移动应用中使用。
-
缺点:
- 撤销困难: 一旦 JWT 被签发,就无法主动撤销。只能等待 JWT 过期。
- Payload 可解码: Payload 中的数据是可解码的,所以不要存放敏感信息。
- 体积较大: 相对于传统的 Session ID,JWT 的体积较大,会增加网络传输的开销。
JWT 的最佳实践:保命指南
- 使用 HTTPS: 确保 JWT 在传输过程中被加密,防止被窃取。
- 使用强密钥: 使用足够长的、随机的密钥,提高 JWT 的安全性。
- 设置合理的过期时间: JWT 的过期时间不宜过长,防止被长期滥用。
- 不要在 Payload 中存放敏感信息: Payload 中的数据是可解码的,所以不要存放密码、银行卡号等敏感信息。
- 考虑使用刷新令牌(Refresh Token): 刷新令牌可以用于在 JWT 过期后,自动获取新的 JWT,提高用户体验。但是要小心刷新令牌的安全问题。可以将刷新令牌存放在 HttpOnly Cookie 中,防止 XSS 攻击。
- 对重要操作进行二次验证: 对于涉及到资金、隐私等重要操作,即使 JWT 验证通过,也应该进行二次验证,比如短信验证码、人脸识别等。
JWT 的一些高级用法:进阶之路
- 使用刷新令牌(Refresh Token): 解决 JWT 无法主动撤销的问题。
- 使用 JWE (JSON Web Encryption): 对 JWT 进行加密,保护 Payload 中的敏感信息。
- 使用 JWKS (JSON Web Key Set): 用于分发公钥,方便客户端验证 JWT 的签名。
JWT 和 OAuth 2.0 的关系:好基友一生走
OAuth 2.0 是一个授权框架,它允许第三方应用代表用户访问受保护的资源。JWT 经常被用作 OAuth 2.0 中的访问令牌 (Access Token)。
OAuth 2.0 定义了如何获取访问令牌,而 JWT 定义了访问令牌的格式和内容。
总结:JWT,你的安全小助手
JWT 是一个强大的认证和授权工具,可以用于构建安全的 Web 应用和 API。但是,在使用 JWT 时,一定要注意安全问题,遵循最佳实践,才能充分发挥 JWT 的优势。
表格:JWT 认证流程
| 步骤 | 描述
| 1. 客户端发起请求 | 用户在浏览器或移动应用中,输入用户名和密码,或者使用其他认证方式(如 OAuth 授权)。