上一篇我们已经让 React 成功请求 /api/login。
但是还有一个关键问题:
用户登录成功以后,服务器怎么知道这个用户是谁?
这就要理解一个很重要的概念:
HTTP 是无状态的。
一、什么叫"无状态"?
可以把两次请求简单理解成两次完全独立的交流。
第一次:
text
用户:
你好,我是 admin,密码是 123456。
服务器:
好的,你登录成功了。
请求结束。
然后第二次:
text
用户:
我要访问 /api/repo。
服务器:
你是谁?
服务器不会因为刚才收到了一个登录请求,就自动永久记住你。
因此:
用户第一次登录成功之后,必须拿到一个"凭证"。
下一次请求带上这个凭证,服务器才能判断:
这个请求是谁发来的。
这个凭证就是:
Token。
二、登录成功后,服务器返回 Token
我们现在修改 /api/login。
首先安装并引入:
js
import jwt from 'jsonwebtoken';
然后准备一个服务器端的秘密:
js
const secret = 'secret819!$';
这个值可以简单理解成:
只有服务器知道的密钥。
三、使用 jwt.sign() 生成 Token
登录验证通过之后:
js
const token = jwt.sign(
{
user: body.username,
role: 'admin'
},
secret,
{
expiresIn: 86400
}
)
这里最重要的是:
js
{
user: body.username,
role: 'admin'
}
这是我们希望服务器记录在 Token 中的身份信息。
然后:
js
jwt.sign(...)
把这个身份对象签发成一个 JWT。
所以可以这样理解:
text
登录成功
↓
得到用户身份信息
↓
jwt.sign()
↓
生成 JWT Token
四、JWT 到底是什么?
JWT 全称:
text
JSON Web Token
你现在先不用死记它的结构。
只需要先理解:
JWT 是一种把用户身份信息放进 Token 中,并且可以由服务器验证真伪的方案。
例如我们有:
js
{
user: 'admin',
role: 'admin'
}
服务器签发之后得到一串:
text
eyJhbGciOiJIUzI1Ni...
以后用户拿着这串 Token 再来请求。
服务器可以通过:
js
jwt.verify()
验证它。
五、把 Token 返回给前端
登录成功之后:
js
return {
code: 0,
user: {
username: body.username
},
token
}
现在浏览器拿到:
json
{
"code": 0,
"user": {
"username": "admin"
},
"token": "eyJ..."
}
登录流程变成:
text
用户填写账号密码
↓
发送 /api/login
↓
服务器验证用户名密码
↓
jwt.sign() 生成 Token
↓
Token 返回给前端
六、Token 不是"登录状态本身"
这是一个很容易混淆的地方。
登录成功以后,服务器给你 Token。
Token 本质上是:
以后访问服务器时,用来证明身份的凭证。
所以:
text
登录
和:
text
鉴权
其实是两个阶段。
登录:
你是谁?密码对不对?
鉴权:
你之前登录过吗?这个 Token 是合法的吗?
七、下一次请求怎么带 Token?
HTTP 中有一个专门的请求 Header:
text
Authorization
通常写成:
text
Authorization: Bearer eyJ...
这里:
text
Bearer
可以简单理解成:
后面的这串内容就是我用于身份验证的 Token。
所以服务器收到:
text
Authorization: Bearer eyJ...
之后,就可以把 Token 取出来验证。
八、服务器如何验证?
/api/repo 收到请求以后:
js
const authorization = req.headers?.authorization;
拿到:
text
Bearer eyJ...
先检查格式:
js
if (!authorization?.startsWith('Bearer ')) {
return {
code: 401,
message: 'Missing authorization token'
};
}
然后取出真正的 Token:
js
const token = authorization.slice('Bearer '.length);
最后:
js
try {
const decoded = jwt.verify(token, secret);
return {
code: 0,
data: decoded.user
};
} catch {
return {
code: 401,
message: 'Invalid token'
};
}
这就是:
text
收到 Authorization
↓
取出 Bearer 后面的 Token
↓
jwt.verify()
↓
验证 Token
↓
成功:允许访问
失败:返回 401
九、为什么同一个 secret 很重要?
生成 Token 时:
js
jwt.sign(payload, secret)
验证 Token 时:
js
jwt.verify(token, secret)
两边必须使用同一个 secret。
否则:
text
sign 用 secret A
verify 用 secret B
服务器就会认为:
这个 Token 不是我签发的。
然后返回:
text
Invalid token
这也是你之前调试时遇到的问题之一。
十、现在 JWT 的核心就清楚了
整套逻辑其实只有两个动作:
text
jwt.sign()
负责:
服务器颁发 Token。
text
jwt.verify()
负责:
服务器验证 Token。
所以不要把 JWT 想得太复杂。
它在这个 demo 里就是围绕这两个动作展开的。