Q15: 什么是 JWT (JSON Web Token)?它的结构是什么?如何在 Node.js 中使用它进行认证?

62 阅读4分钟

Node.js 面试题详细答案 - Q15

Q15: 什么是 JWT (JSON Web Token)?它的结构是什么?如何在 Node.js 中使用它进行认证?

JWT 概述

JWT 是一种开放标准,用于在各方之间安全地传输信息作为 JSON 对象。

JWT 结构

1. 三部分组成
// JWT 结构:header.payload.signature
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

// 1. Header (头部)
{
  "alg": "HS256",    // 算法
  "typ": "JWT"       // 类型
}

// 2. Payload (载荷)
{
  "sub": "1234567890",           // 主题
  "name": "John Doe",            // 用户信息
  "iat": 1516239022,            // 签发时间
  "exp": 1516242622             // 过期时间
}

// 3. Signature (签名)
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret
)
2. 标准声明
// 标准声明 (Standard Claims)
{
  "iss": "issuer",           // 签发者
  "sub": "subject",          // 主题
  "aud": "audience",         // 受众
  "exp": 1516239022,         // 过期时间
  "nbf": 1516239022,         // 生效时间
  "iat": 1516239022,         // 签发时间
  "jti": "jwt-id"            // JWT ID
}

在 Node.js 中使用 JWT

1. 安装依赖
npm install jsonwebtoken
npm install --save-dev @types/jsonwebtoken  # TypeScript
2. 基本使用
const jwt = require('jsonwebtoken')

// 密钥
const secret = 'your-secret-key'

// 生成 JWT
function generateToken(payload) {
  return jwt.sign(payload, secret, { expiresIn: '1h' })
}

// 验证 JWT
function verifyToken(token) {
  try {
    return jwt.verify(token, secret)
  } catch (error) {
    throw new Error('无效的令牌')
  }
}

// 使用示例
const user = { id: 1, name: 'John' }
const token = generateToken(user)
console.log('生成的令牌:', token)

const decoded = verifyToken(token)
console.log('解码后的数据:', decoded)

实际应用场景

1. 用户认证
const express = require('express')
const jwt = require('jsonwebtoken')
const app = express()

const secret = 'your-secret-key'

// 登录接口
app.post('/login', (req, res) => {
  const { username, password } = req.body

  // 验证用户凭据
  if (username === 'admin' && password === 'password') {
    const payload = {
      id: 1,
      username: 'admin',
      role: 'admin',
    }

    const token = jwt.sign(payload, secret, { expiresIn: '1h' })

    res.json({
      success: true,
      token: token,
    })
  } else {
    res.status(401).json({
      success: false,
      message: '无效的凭据',
    })
  }
})

// 受保护的路由
app.get('/protected', authenticateToken, (req, res) => {
  res.json({
    message: '受保护的内容',
    user: req.user,
  })
})

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

  if (!token) {
    return res.status(401).json({ message: '未提供令牌' })
  }

  jwt.verify(token, secret, (err, user) => {
    if (err) {
      return res.status(403).json({ message: '无效的令牌' })
    }

    req.user = user
    next()
  })
}

app.listen(3000)
2. 角色权限控制
// 角色权限中间件
function requireRole(role) {
  return (req, res, next) => {
    if (req.user.role !== role) {
      return res.status(403).json({ message: '权限不足' })
    }
    next()
  }
}

// 管理员路由
app.get('/admin', authenticateToken, requireRole('admin'), (req, res) => {
  res.json({ message: '管理员内容' })
})

// 用户路由
app.get('/user', authenticateToken, requireRole('user'), (req, res) => {
  res.json({ message: '用户内容' })
})
3. 刷新令牌
// 生成访问令牌和刷新令牌
function generateTokens(user) {
  const accessToken = jwt.sign(
    { id: user.id, username: user.username },
    secret,
    { expiresIn: '15m' }
  )

  const refreshToken = jwt.sign({ id: user.id, type: 'refresh' }, secret, {
    expiresIn: '7d',
  })

  return { accessToken, refreshToken }
}

// 刷新令牌接口
app.post('/refresh', (req, res) => {
  const { refreshToken } = req.body

  if (!refreshToken) {
    return res.status(401).json({ message: '未提供刷新令牌' })
  }

  jwt.verify(refreshToken, secret, (err, decoded) => {
    if (err || decoded.type !== 'refresh') {
      return res.status(403).json({ message: '无效的刷新令牌' })
    }

    const newAccessToken = jwt.sign(
      { id: decoded.id, username: decoded.username },
      secret,
      { expiresIn: '15m' }
    )

    res.json({ accessToken: newAccessToken })
  })
})

高级用法

1. 自定义中间件
// 高级认证中间件
function createAuthMiddleware(options = {}) {
  const { secret, algorithms = ['HS256'], issuer, audience } = options

  return (req, res, next) => {
    const token = extractToken(req)

    if (!token) {
      return res.status(401).json({ message: '未提供令牌' })
    }

    const options = { algorithms }
    if (issuer) options.issuer = issuer
    if (audience) options.audience = audience

    jwt.verify(token, secret, options, (err, decoded) => {
      if (err) {
        return res.status(403).json({ message: '无效的令牌' })
      }

      req.user = decoded
      next()
    })
  }
}

// 提取令牌
function extractToken(req) {
  const authHeader = req.headers['authorization']
  if (authHeader && authHeader.startsWith('Bearer ')) {
    return authHeader.substring(7)
  }
  return null
}
2. 令牌黑名单
// 令牌黑名单
const tokenBlacklist = new Set()

// 登出接口
app.post('/logout', (req, res) => {
  const token = extractToken(req)
  if (token) {
    tokenBlacklist.add(token)
  }
  res.json({ message: '登出成功' })
})

// 检查黑名单中间件
function checkBlacklist(req, res, next) {
  const token = extractToken(req)
  if (token && tokenBlacklist.has(token)) {
    return res.status(401).json({ message: '令牌已失效' })
  }
  next()
}
3. 多环境配置
// 环境配置
const config = {
  development: {
    secret: 'dev-secret',
    expiresIn: '24h',
  },
  production: {
    secret: process.env.JWT_SECRET,
    expiresIn: '1h',
  },
}

const env = process.env.NODE_ENV || 'development'
const jwtConfig = config[env]

// 使用配置
function generateToken(payload) {
  return jwt.sign(payload, jwtConfig.secret, {
    expiresIn: jwtConfig.expiresIn,
  })
}

安全考虑

1. 密钥管理
// 使用环境变量
const secret = process.env.JWT_SECRET || 'fallback-secret'

// 使用 RSA 密钥
const privateKey = fs.readFileSync('private.pem')
const publicKey = fs.readFileSync('public.pem')

// 生成令牌
const token = jwt.sign(payload, privateKey, {
  algorithm: 'RS256',
  expiresIn: '1h',
})

// 验证令牌
const decoded = jwt.verify(token, publicKey, {
  algorithms: ['RS256'],
})
2. 令牌安全
// 安全的令牌生成
function generateSecureToken(user) {
  const payload = {
    sub: user.id,
    username: user.username,
    role: user.role,
    iat: Math.floor(Date.now() / 1000),
    exp: Math.floor(Date.now() / 1000) + 60 * 60, // 1小时
    jti: generateUniqueId(), // 唯一标识符
  }

  return jwt.sign(payload, secret, {
    algorithm: 'HS256',
    issuer: 'your-app',
    audience: 'your-users',
  })
}

错误处理

1. 令牌验证错误
// 错误处理中间件
function handleJWTError(err, req, res, next) {
  if (err.name === 'JsonWebTokenError') {
    return res.status(401).json({ message: '无效的令牌' })
  }

  if (err.name === 'TokenExpiredError') {
    return res.status(401).json({ message: '令牌已过期' })
  }

  if (err.name === 'NotBeforeError') {
    return res.status(401).json({ message: '令牌尚未生效' })
  }

  next(err)
}

// 使用错误处理
app.use(handleJWTError)

总结

  • JWT 结构:Header.Payload.Signature 三部分组成
  • 标准声明:iss, sub, aud, exp, nbf, iat, jti
  • 基本使用:jwt.sign() 生成,jwt.verify() 验证
  • 认证流程:登录生成令牌,请求携带令牌,中间件验证
  • 安全考虑:密钥管理、令牌过期、黑名单机制
  • 最佳实践:使用环境变量、错误处理、角色权限控制
  • 应用场景:用户认证、API 授权、单点登录