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 授权、单点登录