Skip to content

token从入门到精通

对于token想必大家都不陌生,因为在做微信网页开发的时候避免不了的要学习微信那一套授权机制,又是accessToken又是refreshToken,那么token是什么呢?

一、什么是token

Token 是一种用户身份验证机制,通常被称为“令牌”。当用户首次登录时,服务器会生成一个 Token 并返回给客户端。此后,客户端在请求数据时只需携带该 Token,无需再次输入用户名和密码。 alt text

二、token的特点

1. 无状态性(Stateless)

  • 服务器不需要存储用户的会话信息,每次请求都通过 Token 进行身份验证。
  • 适用于分布式系统,减少服务器的存储压力。

2. 便携性(Portable)

  • Token 可以存储在客户端(如 localStoragesessionStorageCookie),并随请求一起发送。
  • 适用于 Web、移动端、API 请求等多个场景。

3. 访问控制(Access Control)

  • Token 可包含用户权限信息,服务器可根据 Token 判断用户是否有权限访问某些资源。
  • 例如:admin 角色可以管理用户,而 user 角色只能查看数据。

4. 时效性(Expiration)

  • Token 通常有有效期,防止长期有效带来的安全风险。
  • 例如 access_token 可能 2 小时过期,refresh_token 可能 30 天有效。

5. 可刷新性(Refreshable)

  • 通过 refresh_token 机制,可以在 access_token 过期后,无需用户重新登录,直接获取新的 access_token

6. 安全性(Security)

  • 需要妥善存储,避免 Token 泄露,防止恶意用户伪造 Token 进行请求。
  • 传输时一般使用 HTTPS,防止中间人攻击(MITM)。

7. 兼容性(Cross-domain Support)

  • Token 适用于跨域身份验证,不受浏览器同源策略的限制,适合前后端分离的架构。”

三、token的组成

Token 的具体内容取决于使用场景和实现方式,但通常包含以下几类信息:

1. 唯一标识 (User ID / Session ID)

  • 用于标识用户或会话的唯一 ID,如 userIdsessionId 等。
  • 例如:user_id=12345

2. 访问权限 (Scope / Role / Permissions)

  • 定义该 Token 允许访问的资源或操作权限,如用户角色(adminuser)或具体权限(readwrite)。
  • 例如:role=adminpermissions=["read", "write"]

3. 过期时间 (Expiration Time, Expiry Date)

  • Token 的有效期,通常包含 expiresAtttl(time-to-live) 字段,防止长期有效带来的安全风险。
  • 例如:expires_at=1712000000(Unix 时间戳)

4. 发行信息 (Issuer, Issued Time)

  • Token 的签发者信息(issuer),通常是服务端的标识。
  • 例如:issuer=auth.example.com
  • 签发时间(issued_at),记录 Token 生成的时间。
  • 例如:issued_at=1711000000

5. 客户端信息 (Device / IP / User Agent)

  • 一些 Token 可能包含设备 ID、用户 IP 地址等信息,以增加安全性和防止 Token 滥用。
  • 例如:device_id="abcd1234"ip="192.168.1.1"

6 签名 / 校验信息(可选)

  • 在某些 Token 实现中,会包含哈希签名(如 HMAC、SHA256)或校验码,防止篡改。
  • 例如:signature="abc123xyz..."

四、token的基本用法

  1. namevalue

    • name: cookie 的名称(必需)。
    • value: cookie 的值(必需)。
    javascript
    document.cookie = "username=John";
javascript
{
  "user_id": "12345",
  "role": "admin",
  "permissions": ["read", "write"],
  "expires_at": 1712000000,
  "issued_at": 1711000000,
  "issuer": "auth.example.com",
  "device_id": "abcd1234",
  "ip": "192.168.1.1"
}

五、token(accesstoken)的身份验证流程

alt text

  • 客户端使用用户名跟密码请求登录
  • 服务端收到请求,去验证用户名与密码
  • 验证成功后,服务端会签发一个 token 并把这个 token 发送给客户端
  • 客户端收到 token 以后,会把它存储起来,比如放在 cookie 里或者 localStorage 里
  • 客户端每次向服务端请求资源的时候需要带着服务端签发的 token
  • 服务端收到请求,然后去验证客户端请求里面带着的 token ,如果验证成功,就向客户端返回请求的数据

每一次请求都需要携带 token,需要把 token 放到 HTTP 的 Header 里 基于 token 的用户认证是一种服务端无状态的认证方式,服务端不用存放 token 数据。用解析 token 的计算时间换取 session 的存储空间,从而减轻服务器的压力,减少频繁的查询数据库 token 完全由应用管理,所以它可以避开同源策略

六、refreshToken 用法

refresh token 是专用于刷新 access token 的 token。如果没有 refresh token,也可以刷新 access token,但每次刷新都要用户输入登录用户名与密码,会很麻烦。有了 refresh token,可以减少这个麻烦,客户端直接用 refresh token 去更新 access token,无需用户进行额外的操作。 alt text

  • Access Token 的有效期比较短,当 Acesss Token 由于过期而失效时,使用 Refresh Token 就可以获取到新的 Token,如果 Refresh Token 也失效了,用户就只能重新登录了。
  • Refresh Token 及过期时间是存储在服务器的数据库中,只有在申请新的 Acesss Token 时才会验证,不会对业务接口响应时间造成影响,也不需要向 Session 一样一直保持在内存中以应对大量的请求。

alt text

七、完整demo

  • 安装依赖
js
  npm init -y
  npm install express jsonwebtoken dotenv cookie-parser
  • 创建 server.js
js
require("dotenv").config();
const express = require("express");
const jwt = require("jsonwebtoken");
const cookieParser = require("cookie-parser");

const app = express();
app.use(express.json());
app.use(cookieParser());

// 模拟数据库存储 refreshToken
const refreshTokensDB = new Map();

// 生成 accessToken
function generateAccessToken(user) {
  return jwt.sign(
    {
      userId: user.id,
      role: user.role,
      issuedAt: Date.now(),
      expiresAt: Date.now() + 15 * 60 * 1000, // 15 分钟
      issuer: "auth.example.com",
      deviceId: user.deviceId,
    },
    process.env.ACCESS_TOKEN_SECRET,
    { expiresIn: "15m" },
  );
}

// 生成 refreshToken
function generateRefreshToken(user) {
  const refreshToken = jwt.sign(
    { userId: user.id },
    process.env.REFRESH_TOKEN_SECRET,
    { expiresIn: "7d" }, // 7 天
  );

  refreshTokensDB.set(user.id, refreshToken);
  return refreshToken;
}

// 用户登录
app.post("/login", (req, res) => {
  const { username, password, deviceId } = req.body;

  // 模拟用户认证
  if (username !== "admin" || password !== "password") {
    return res.status(401).json({ error: "Invalid credentials" });
  }

  const user = { id: "12345", role: "admin", deviceId };
  const accessToken = generateAccessToken(user);
  const refreshToken = generateRefreshToken(user);

  res.cookie("refreshToken", refreshToken, { httpOnly: true, secure: true });
  res.json({ accessToken, expiresIn: 15 * 60 });
});

// 受保护接口
app.get("/protected", authenticateToken, (req, res) => {
  res.json({ message: "Protected data", user: req.user });
});

// Token 验证中间件
function authenticateToken(req, res, next) {
  const authHeader = req.headers["authorization"];
  const token = authHeader && authHeader.split(" ")[1];

  if (!token) return res.sendStatus(401);

  jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, user) => {
    if (err) return res.sendStatus(403);
    req.user = user;
    next();
  });
}

// 自动刷新 accessToken
app.post("/refresh", (req, res) => {
  const refreshToken = req.cookies.refreshToken;

  if (!refreshToken) return res.sendStatus(401);

  jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET, (err, user) => {
    if (err || !refreshTokensDB.has(user.userId)) return res.sendStatus(403);

    const newAccessToken = generateAccessToken(user);
    res.json({ accessToken: newAccessToken, expiresIn: 15 * 60 });
  });
});

// 退出登录
app.post("/logout", (req, res) => {
  const refreshToken = req.cookies.refreshToken;
  refreshTokensDB.delete(refreshToken);
  res.clearCookie("refreshToken");
  res.json({ message: "Logged out" });
});

app.listen(3000, () => console.log("Server running on port 3000"));
  • 创建.env文件
js
ACCESS_TOKEN_SECRET = your_access_token_secret;
REFRESH_TOKEN_SECRET = your_refresh_token_secret;
  • 启动服务
js
node server.js

八、使用 Postman 测试

  • 登录
js
POST http://localhost:3000/login
Content-Type: application/json

{
  "username": "admin",
  "password": "password",
  "deviceId": "device-123"
}
  • postman返回
js
{
  "accessToken": "eyJhbGciOi...",
  "expiresIn": 900
}
  • 访问受保护接口
js
GET http://localhost:3000/protected
Authorization: Bearer <accessToken>
  • 自动刷新 Token 当 accessToken 过期前,客户端调用:
js
POST http://localhost:3000/refresh

返回新的 accessToken。

  • 退出登录
js
POST http://localhost:3000/logout

✅ accessToken

15 分钟有效,携带 userId、role、expiresAt 等信息

通过 Authorization: Bearer token 访问 API

✅ refreshToken

7 天有效,存储在 cookie,用于刷新 accessToken

只能在 /refresh 接口使用

✅ 自动续期

accessToken 过期前,客户端请求 /refresh 获取新 Token

✅ 安全性

refreshToken 存在服务器中,防止滥用

accessToken 过期后不可用,减少风险

参考文章:

微信网页授权

源码链接

https://github.com/lecheng-lc/demo

上次更新于: