• 微信:WANCOME
  • 扫码加微信,提供专业咨询
  • 服务热线
  • 13215191218
    13027920428

  • 微信扫码访问本页
desc-2
环企首页

jsonwebtoken (JWT)

概述

JWT(JSON Web Token)是目前最流行的跨域身份认证方案。核心思想是:服务端不存储 Token,而是通过签名验证客户端发来的 Token 是否可信

JWT 结构:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4ifQ.kKeG9z
 ^header                          ^payload                         ^signature

三部分 Base64URL 编码,用 . 连接。服务端只需验证签名,无法伪造或篡改。

适用场景:

  • 前后端分离的无状态身份认证
  • 跨域授权
  • 单点登录(SSO)
  • API 访问令牌

不适用的场景:

  • 敏感信息存储(Payload 仅 Base64 编码,可被解码,不要存密码等敏感数据
  • 需要服务端强制失效的场景(用 Redis 黑名单)

核心函数

jwt.sign(payload, secretOrPrivateKey, options)

生成 Token。

const jwt = require('jsonwebtoken')
const secret = process.env.JWT_SECRET  // 必须足够长且随机

// 基础用法(HS256 对称签名,最常用)
const token = jwt.sign(
  { userId: 123, role: 'admin' },     // payload(载荷)
  secret,                             // 密钥
  { expiresIn: '7d' }                 // 选项
)

// payload 常用标准字段(claims)
const payload = {
  sub: '1234567890',                  // subject(用户 ID)
  name: '张三',                        // 自定义字段
  role: 'user',
  iat: Math.floor(Date.now() / 1000), // issued at(签发时间,自动添加)
  exp: Math.floor(Date.now() / 1000) + 7 * 24 * 60 * 60  // 过期时间
}

// 过期时间写法
{ expiresIn: '2 hours' }              // '1h', '7d', '30s', '2 days'
{ expiresIn: 3600 }                   // 秒数
{ expiresIn: '2 days', algorithm: 'HS256' }

// RS256 非对称签名(私钥签,公钥验,适合多服务端场景)
const privateKey = fs.readFileSync('private.pem')
const publicKey = fs.readFileSync('public.pem')

const token = jwt.sign({ userId: 123 }, privateKey, {
  algorithm: 'RS256',
  expiresIn: '1h'
})

jwt.verify(token, secretOrPublicKey, options)

验证并解析 Token。

// 同步验证(推荐)
try {
  const decoded = jwt.verify(token, secret)
  console.log(decoded.userId)  // 自动解析 payload
  console.log(decoded.iat)     // 签发时间
  console.log(decoded.exp)     // 过期时间
} catch (err) {
  // Token 无效(过期/伪造/格式错误)
  if (err.name === 'TokenExpiredError') {
    // Token 过期
  } else if (err.name === 'JsonWebTokenError') {
    // 签名不匹配或格式错误
  } else if (err.name === 'NotBeforeError') {
    // Token 尚未生效
  }
}

// 异步验证
jwt.verify(token, secret, (err, decoded) => {
  if (err) return res.status(401).json({ error: '无效 Token' })
  req.user = decoded
})

jwt.decode(token, options)

仅解析 Token(不验证签名),仅用于读取 Payload。

// 不验证签名,直接解析(适合调试)
const decoded = jwt.decode(token)

// 仅获取 header
const header = jwt.decode(token, { complete: true }).header

// 适用于验证格式但不验证签名的场景(如从 URL 参数解析)

完整使用流程

1. 签发 Token(登录时)

app.post('/login', async (req, res) => {
  const { username, password } = req.body
  
  const user = await db.findUser(username)
  if (!user || !await bcrypt.compare(password, user.passwordHash)) {
    return res.status(401).json({ error: '用户名或密码错误' })
  }
  
  const token = jwt.sign(
    {
      userId: user.id,
      role: user.role,
      name: user.name
    },
    process.env.JWT_SECRET,
    { expiresIn: '7d' }
  )
  
  res.json({ token })
})

2. 验证 Token(中间件)

// 方式1:手动解析(推荐,方便控制)
const authMiddleware = (req, res, next) => {
  const authHeader = req.headers['authorization']
  
  if (!authHeader) {
    return res.status(401).json({ error: '未提供 Token' })
  }
  
  const token = authHeader.replace('Bearer ', '')
  
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET)
    req.user = decoded  // 后续路由可用
    next()
  } catch (err) {
    if (err.name === 'TokenExpiredError') {
      return res.status(401).json({ error: 'Token 已过期,请重新登录' })
    }
    return res.status(401).json({ error: '无效的 Token' })
  }
}

// 方式2:用 express-jwt 库(自动中间件)
const { expressjwt: jwt } = require('express-jwt')
const jwtMiddleware = jwt({
  secret: process.env.JWT_SECRET,
  algorithms: ['HS256'],
  requestProperty: 'auth'           // 解析后挂载到 req.auth
})

3. 受保护路由

app.get('/profile', authMiddleware, (req, res) => {
  res.json({ user: req.user })
})

// 管理员专属
app.delete('/admin/user/:id', authMiddleware, (req, res) => {
  if (req.user.role !== 'admin') {
    return res.status(403).json({ error: '权限不足' })
  }
  // 执行删除
})

// 刷新 Token(过期前检查)
app.post('/refresh-token', authMiddleware, (req, res) => {
  const newToken = jwt.sign(
    { userId: req.user.userId },
    process.env.JWT_SECRET,
    { expiresIn: '7d' }
  )
  res.json({ token: newToken })
})

Token 安全策略

// 1. HTTPS 传输(生产必须)
// 2. 设置合理的过期时间
const token = jwt.sign({ ... }, secret, { expiresIn: '15m' })  // 访问令牌(短)
const refreshToken = jwt.sign({ ... }, secret, { expiresIn: '7d' })  // 刷新令牌(长)

// 3. Token 黑名单(强制失效)
const blacklist = new Set()
app.post('/logout', authMiddleware, (req, res) => {
  blacklist.add(req.token)
  res.json({ success: true })
})

// 中间件检查黑名单
if (blacklist.has(req.token)) {
  return res.status(401).json({ error: 'Token 已失效' })
}

// 4. 密钥管理
// 永远不要硬编码密钥
// 生产环境使用环境变量
// 定期轮换密钥(注意旧 Token 失效问题)

常见问题

问题 解决方案
Token 过期后仍然可用 Token 是无状态的,只能通过检查 exp 字段验证
想让 Token 立即失效 将 Token 加入 Redis 黑名单(带 TTL)
多服务器共享验证 所有服务器用相同的 JWT_SECRET
密钥泄露 立即更换 JWT_SECRET,通知用户重新登录
Payload 被篡改 不可能,签名验证会失败
Payload 能被看到 对,Payload 仅 Base64 编码,敏感数据不要放进去