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 里就是围绕这两个动作展开的。

相关推荐
东风破_1 小时前
JWT 4:Zustand 到底解决了什么?为什么登录状态要放进 Store?
前端·后端
IT_陈寒1 小时前
Vite动态导入差点让我秃头,原来问题出在这
前端·人工智能·后端
白泽_hunter2 小时前
项目里最常见的 5 个 this 指向坑:从规则到实战彻底讲透
前端
宿6742 小时前
vue3-pinia
前端·vue.js
白泽_hunter2 小时前
搞懂 JS 数据类型:从 typeof 到手写完整类型判断,一张图理清
前端
唐青枫2 小时前
别只把大括号当作用域:Zig Block、标签块与控制流实战
后端
日光倾2 小时前
TypeScript 随手记 —— 1
前端·javascript·typescript
自学it的攻城狮3 小时前
elpis-core ,koa实现的系统底座, 沉淀80% 的通用能力,剩下20% 用于定制化开发
前端
CappuccinoRose3 小时前
模块化体系
前端·import·export·es modules