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

0 阅读3分钟

上一篇我们已经让 React 成功请求 /api/login

但是还有一个关键问题:

用户登录成功以后,服务器怎么知道这个用户是谁?

这就要理解一个很重要的概念:

HTTP 是无状态的。


一、什么叫“无状态”?

可以把两次请求简单理解成两次完全独立的交流。

第一次:

用户:
你好,我是 admin,密码是 123456。

服务器:
好的,你登录成功了。

请求结束。

然后第二次:

用户:
我要访问 /api/repo。

服务器:
你是谁?

服务器不会因为刚才收到了一个登录请求,就自动永久记住你。

因此:

用户第一次登录成功之后,必须拿到一个“凭证”。

下一次请求带上这个凭证,服务器才能判断:

这个请求是谁发来的。

这个凭证就是:

Token。


二、登录成功后,服务器返回 Token

我们现在修改 /api/login

首先安装并引入:

import jwt from 'jsonwebtoken';

然后准备一个服务器端的秘密:

const secret = 'secret819!$';

这个值可以简单理解成:

只有服务器知道的密钥。


三、使用 jwt.sign() 生成 Token

登录验证通过之后:

const token = jwt.sign(
  {
    user: body.username,
    role: 'admin'
  },
  secret,
  {
    expiresIn: 86400
  }
)

这里最重要的是:

{
  user: body.username,
  role: 'admin'
}

这是我们希望服务器记录在 Token 中的身份信息。

然后:

jwt.sign(...)

把这个身份对象签发成一个 JWT。

所以可以这样理解:

登录成功
    ↓
得到用户身份信息
    ↓
jwt.sign()
    ↓
生成 JWT Token

四、JWT 到底是什么?

JWT 全称:

JSON Web Token

你现在先不用死记它的结构。

只需要先理解:

JWT 是一种把用户身份信息放进 Token 中,并且可以由服务器验证真伪的方案。

例如我们有:

{
  user: 'admin',
  role: 'admin'
}

服务器签发之后得到一串:

eyJhbGciOiJIUzI1Ni...

以后用户拿着这串 Token 再来请求。

服务器可以通过:

jwt.verify()

验证它。


五、把 Token 返回给前端

登录成功之后:

return {
  code: 0,
  user: {
    username: body.username
  },
  token
}

现在浏览器拿到:

{
  "code": 0,
  "user": {
    "username": "admin"
  },
  "token": "eyJ..."
}

登录流程变成:

用户填写账号密码
        ↓
发送 /api/login
        ↓
服务器验证用户名密码
        ↓
jwt.sign() 生成 Token
        ↓
Token 返回给前端

六、Token 不是“登录状态本身”

这是一个很容易混淆的地方。

登录成功以后,服务器给你 Token。

Token 本质上是:

以后访问服务器时,用来证明身份的凭证。

所以:

登录

和:

鉴权

其实是两个阶段。

登录:

你是谁?密码对不对?

鉴权:

你之前登录过吗?这个 Token 是合法的吗?


七、下一次请求怎么带 Token?

HTTP 中有一个专门的请求 Header:

Authorization

通常写成:

Authorization: Bearer eyJ...

这里:

Bearer

可以简单理解成:

后面的这串内容就是我用于身份验证的 Token。

所以服务器收到:

Authorization: Bearer eyJ...

之后,就可以把 Token 取出来验证。


八、服务器如何验证?

/api/repo 收到请求以后:

const authorization = req.headers?.authorization;

拿到:

Bearer eyJ...

先检查格式:

if (!authorization?.startsWith('Bearer ')) {
  return {
    code: 401,
    message: 'Missing authorization token'
  };
}

然后取出真正的 Token:

const token = authorization.slice('Bearer '.length);

最后:

try {
  const decoded = jwt.verify(token, secret);

  return {
    code: 0,
    data: decoded.user
  };
} catch {
  return {
    code: 401,
    message: 'Invalid token'
  };
}

这就是:

收到 Authorization
        ↓
取出 Bearer 后面的 Token
        ↓
jwt.verify()
        ↓
验证 Token
        ↓
成功:允许访问
失败:返回 401

九、为什么同一个 secret 很重要?

生成 Token 时:

jwt.sign(payload, secret)

验证 Token 时:

jwt.verify(token, secret)

两边必须使用同一个 secret。

否则:

sign 用 secret A
verify 用 secret B

服务器就会认为:

这个 Token 不是我签发的。

然后返回:

Invalid token

这也是你之前调试时遇到的问题之一。


十、现在 JWT 的核心就清楚了

整套逻辑其实只有两个动作:

jwt.sign()

负责:

服务器颁发 Token。

jwt.verify()

负责:

服务器验证 Token。

所以不要把 JWT 想得太复杂。

它在这个 demo 里就是围绕这两个动作展开的。