JWT Token 入门:原理 + 完整代码实战(Node.js)
·
前言
如果你刚开始学后端,一定会遇到一个词:Token。登录要用它,接口要验它,到处都是它,但没人告诉你它到底是什么。
这篇文章用最直白的方式,从"为什么需要 Token"讲起,带你走完整个流程。看完你就能在自己的项目里用起来。
一、Token 是什么?
想象一个场景:你去酒店入住,前台验证你的身份证后,给你一张房卡。之后你在酒店里刷房卡就行,不用每次都掏身份证。Token 就是这张"房卡"。
- 用户登录时,提交用户名+密码(出示身份证)
- 服务器验证通过后,生成一个 Token 字符串返回给客户端(发房卡)
- 之后客户端每次请求都带上这个 Token(刷房卡)
- 服务器验证 Token 合法就放行(房卡有效就开门)
这样服务器就不需要记住"谁登录了",Token 本身就携带了用户信息。
二、最常用的方案:JWT (JSON Web Token)
1.JWT 是一个编码后的字符串,长这样:
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjEsImV4cCI6MTcxMH0.abc123signature
2.结构:由三段组成,用 . 分隔:
Header.Payload.Signature
头部.载荷.签名
- Header:声明加密算法(如 HS256)
- Payload:存放数据(如 userId、过期时间)
- Signature:用服务器的密钥对前两部分签名,防篡改
关键点:Payload 只是 Base64 编码(不是加密),任何人都能解码看到内容。安全性靠的是 Signature — 没有密钥就伪造不了签名。
3.手动解码演示:
# 把 JWT 的第二段(Payload)拿出来,Base64 解码
echo 'eyJ1c2VySWQiOjEsInVzZXJuYW1lIjoiYWxpY2UiLCJpYXQiOjE3MTAwMDAwMDAsImV4cCI6MTcxMDAwNzIwMH0=' | base64 -d
# 输出:{"userId":1,"username":"alice","iat":1710000000,"exp":1710007200}
看到了吧,Payload 就是明文 JSON,谁都能看。所以不要把密码、手机号这类敏感信息放进去。
三、实操演示(Node.js + Express)
// 1. 初始化项目后安装依赖:
// npm init -y
// npm install express jsonwebtoken
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
app.use(express.json());
// 密钥,实际项目中放环境变量里,绝对不能硬编码
const SECRET_KEY = 'my-secret-key-change-in-production';
// 模拟数据库中的用户
const users = [
{ id: 1, username: 'alice', password: '123456' }
];
// ============ 登录接口:验证身份,发 Token ============
app.post('/login', (req, res) => {
const { username, password } = req.body;
// 查找用户(实际项目密码要用 bcrypt 哈希比对)
const user = users.find(
u => u.username === username && u.password === password
);
if (!user) {
return res.status(401).json({ message: '用户名或密码错误' });
}
// 生成 Token,把 userId 放进去,设置 2 小时过期
const token = jwt.sign(
{ userId: user.id, username: user.username },
SECRET_KEY,
{ expiresIn: '2h' }
);
res.json({ token });
});
// ============ 中间件:检查 Token ============
function authMiddleware(req, res, next) {
// 从请求头拿 Token,格式约定:Authorization: Bearer <token>
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ message: '未提供 Token' });
}
const token = authHeader.split(' ')[1]; // 取 Bearer 后面的部分
try {
// 验证 Token:检查签名是否合法、是否过期
const decoded = jwt.verify(token, SECRET_KEY);
req.user = decoded; // 把解码出的用户信息挂到 req 上
next(); // 验证通过,继续执行后续逻辑
} catch (err) {
if (err.name === 'TokenExpiredError') {
return res.status(401).json({ message: 'Token 已过期,请重新登录' });
}
return res.status(401).json({ message: 'Token 无效' });
}
}
// ============ 受保护的接口:需要登录才能访问 ============
app.get('/profile', authMiddleware, (req, res) => {
// req.user 就是从 Token 里解码出来的信息
res.json({
message: '访问成功',
user: req.user // { userId: 1, username: 'alice', iat: ..., exp: ... }
});
});
// 公开接口,不需要 Token
app.get('/public', (req, res) => {
res.json({ message: '这是公开信息,不需要登录' });
});
app.listen(3000, () => console.log('服务运行在 http://localhost:3000'));
测试--用 curl 模拟客户端:
# 1. 登录,拿到 Token
curl -X POST http://localhost:3000/login \
-H "Content-Type: application/json" \
-d '{"username":"alice","password":"123456"}'
# 返回:{"token":"eyJhbGciOi..."}
# 2. 带 Token 访问受保护接口(把下面的 <token> 替换成上一步拿到的)
curl http://localhost:3000/profile \
-H "Authorization: Bearer <token>"
# 返回:{"message":"访问成功","user":{...}}
# 3. 不带 Token 访问
curl http://localhost:3000/profile
# 返回:{"message":"未提供 Token"}
四、流程总结

几个实际开发中的注意点:
- 密钥放环境变量(
process.env.JWT_SECRET),不要写在代码里- 密码存数据库前用 bcrypt 哈希,永远不存明文
- Token 过期时间别设太长,配合 Refresh Token 机制做续期
- HTTPS 是必须的,否则 Token 在传输中会被截获
- Token 一旦签发,服务端无法主动让它失效(除非额外维护黑名单)
五、Token vs Session
简单说:小项目、单服务器用 Session 没问题;分布式、前后端分离、移动端场景,Token 更合适。现在大多数项目选 Token。
| 对比项 | Session | Token (JWT) |
|---|---|---|
| 状态存储 | 服务器保存会话数据 | 服务器不存,信息在 Token 里 |
| 扩展性 | 多台服务器需要共享 Session(如 Redis) | 天然支持分布式,任何服务器都能验证 |
| 跨域 | 依赖 Cookie,跨域麻烦 | 放在 Header 里,跨域友好 |
| 注销 | 服务端删除 Session 即可 | 无法主动失效,需要额外机制 |
结尾
Token 的核心就一句话:用密码换令牌,用令牌证身份。理解了这个思路,不管你用的是 Node.js、Java 还是 Go,换的只是库和语法,逻辑完全一样。建议你把上面的代码跑一遍,用 curl 走一遍登录→带 Token 请求→不带 Token 请求的流程,跑通了就真的懂了。
更多推荐




所有评论(0)