JWT 2:HTTP 是无状态的,为什么登录成功后还要给 Token?

上一篇我们已经让 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 里就是围绕这两个动作展开的。

相关推荐
卷无止境2 小时前
智能体开发环境ADE浅析,编程工具的下一次范式跃迁
后端·python
码事漫谈5 小时前
DeepSeek 这波操作很凶
后端
CharlesYu015 小时前
前端性能优化的第一性原理,是不断缩短“用户发起意图 → 获得可用结果”之间的时间
前端
Bs_MoneyMagnet5 小时前
基于springboot+vue的医疗健康便民服务平台的设计与实现 源码+文档
java·vue.js·spring boot·后端·spring
平头哥技术团队6 小时前
Day 21 _ 页内锚点_给每段起个 id,目录写 href=_#id_,点一下页面就滚到那一段
前端·html·html5
子兮曰7 小时前
Bun v1.4.1 深度解析:从 Zig 到 Rust,一场 11 天、64 个 AI 代理的语言迁徙
前端·后端·bun
人民广场吃泡面7 小时前
什么是AI Agent?它又能给前端带来哪些效率提升?
前端·人工智能
中科三方7 小时前
两家域名注册商资质被ICANN终止:企业域名资产安全再受关注
前端·网络·安全·域名
bug总结8 小时前
uniapp vue3全局方法注册使用
前端·javascript·uni-app
华无丽言8 小时前
如何在宜搭中实现获取子表中的字段值赋值到父表中?
前端·javascript·低代码