引言

API安全是现代应用开发中的核心议题。随着微服务架构的普及,API作为系统间的通信桥梁,其安全性直接影响整个系统的安全。本文将全面讲解API安全的各个方面,从认证授权到防护策略。

一、身份认证机制

1.1 JWT(JSON Web Token)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
// JWT服务实现
import jwt from 'jsonwebtoken'
import bcrypt from 'bcrypt'

interface JWTPayload {
  userId: string
  email: string
  role: string
}

class AuthService {
  private readonly JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key'
  private readonly JWT_REFRESH_SECRET = process.env.JWT_REFRESH_SECRET || 'your-refresh-secret'
  private readonly JWT_EXPIRES_IN = '15m'
  private readonly JWT_REFRESH_EXPIRES_IN = '7d'

  // 生成访问令牌
  generateAccessToken(payload: JWTPayload): string {
    return jwt.sign(payload, this.JWT_SECRET, {
      expiresIn: this.JWT_EXPIRES_IN,
      issuer: 'myapp.com',
      audience: 'myapp-api'
    })
  }

  // 生成刷新令牌
  generateRefreshToken(payload: JWTPayload): string {
    return jwt.sign(
      { userId: payload.userId },
      this.JWT_REFRESH_SECRET,
      { expiresIn: this.JWT_REFRESH_EXPIRES_IN }
    )
  }

  // 验证访问令牌
  verifyAccessToken(token: string): JWTPayload | null {
    try {
      return jwt.verify(token, this.JWT_SECRET) as JWTPayload
    } catch (error) {
      return null
    }
  }

  // 验证刷新令牌
  verifyRefreshToken(token: string): { userId: string } | null {
    try {
      return jwt.verify(token, this.JWT_REFRESH_SECRET) as { userId: string }
    } catch (error) {
      return null
    }
  }

  // 密码哈希
  async hashPassword(password: string): Promise<string> {
    return bcrypt.hash(password, 12)
  }

  // 密码验证
  async comparePassword(password: string, hash: string): Promise<boolean> {
    return bcrypt.compare(password, hash)
  }

  // 登录流程
  async login(email: string, password: string) {
    const user = await this.findUserByEmail(email)
    if (!user) {
      throw new Error('User not found')
    }

    const isValid = await this.comparePassword(password, user.passwordHash)
    if (!isValid) {
      throw new Error('Invalid password')
    }

    const payload: JWTPayload = {
      userId: user.id,
      email: user.email,
      role: user.role
    }

    return {
      accessToken: this.generateAccessToken(payload),
      refreshToken: this.generateRefreshToken(payload),
      expiresIn: 15 * 60  // 15分钟
    }
  }

  // 刷新令牌
  async refreshTokens(refreshToken: string) {
    const payload = this.verifyRefreshToken(refreshToken)
    if (!payload) {
      throw new Error('Invalid refresh token')
    }

    const user = await this.findUserById(payload.userId)
    if (!user) {
      throw new Error('User not found')
    }

    const jwtPayload: JWTPayload = {
      userId: user.id,
      email: user.email,
      role: user.role
    }

    return {
      accessToken: this.generateAccessToken(jwtPayload),
      refreshToken: this.generateRefreshToken(jwtPayload)
    }
  }

  private async findUserByEmail(email: string) {
    // 数据库查询实现
    return null
  }

  private async findUserById(userId: string) {
    // 数据库查询实现
    return null
  }
}

export const authService = new AuthService()

1.2 Express中间件

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
// 认证中间件
import { Request, Response, NextFunction } from 'express'

declare global {
  namespace Express {
    interface Request {
      user?: {
        userId: string
        email: string
        role: string
      }
    }
  }
}

// JWT认证中间件
export function authenticate(req: Request, res: Response, next: NextFunction) {
  const authHeader = req.headers.authorization

  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({
      error: 'Unauthorized',
      message: 'Missing or invalid authorization header'
    })
  }

  const token = authHeader.substring(7)

  const payload = authService.verifyAccessToken(token)
  if (!payload) {
    return res.status(401).json({
      error: 'Unauthorized',
      message: 'Invalid or expired token'
    })
  }

  req.user = payload
  next()
}

// 角色授权中间件
export function authorize(...roles: string[]) {
  return (req: Request, res: Response, next: NextFunction) => {
    if (!req.user) {
      return res.status(401).json({
        error: 'Unauthorized',
        message: 'Authentication required'
      })
    }

    if (!roles.includes(req.user.role)) {
      return res.status(403).json({
        error: 'Forbidden',
        message: 'Insufficient permissions'
      })
    }

    next()
  }
}

// 可选认证中间件(允许未登录用户访问)
export function optionalAuthenticate(req: Request, res: Response, next: NextFunction) {
  const authHeader = req.headers.authorization

  if (authHeader && authHeader.startsWith('Bearer ')) {
    const token = authHeader.substring(7)
    const payload = authService.verifyAccessToken(token)
    if (payload) {
      req.user = payload
    }
  }

  next()
}

// 使用示例
import express from 'express'

const router = express.Router()

// 公开路由
router.get('/public', (req, res) => {
  res.json({ message: 'Public endpoint' })
})

// 需要认证
router.get('/profile', authenticate, (req, res) => {
  res.json({ user: req.user })
})

// 需要特定角色
router.delete('/users/:id', authenticate, authorize('admin'), (req, res) => {
  res.json({ message: 'User deleted' })
})

// 可选认证(登录和未登录都可以访问)
router.get('/content', optionalAuthenticate, (req, res) => {
  if (req.user) {
    res.json({ content: 'premium content', user: req.user })
  } else {
    res.json({ content: 'free content' })
  }
})

1.3 OAuth 2.0实现

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
// OAuth 2.0授权码模式
import crypto from 'crypto'
import { OAuth2Client } from 'google-auth-library'

class OAuthService {
  private clients = new Map()
  private authorizationCodes = new Map()
  private accessTokens = new Map()

  // 注册OAuth客户端
  registerClient(clientId: string, redirectUris: string[], scopes: string[]) {
    const clientSecret = crypto.randomBytes(32).toString('hex')
    this.clients.set(clientId, {
      clientId,
      clientSecret,
      redirectUris,
      scopes
    })
    return { clientId, clientSecret }
  }

  // 生成授权码
  generateAuthorizationCode(userId: string, clientId: string, scopes: string[]): string {
    const code = crypto.randomBytes(32).toString('hex')
    this.authorizationCodes.set(code, {
      userId,
      clientId,
      scopes,
      expiresAt: Date.now() + 10 * 60 * 1000  // 10分钟过期
    })
    return code
  }

  // 验证授权码并生成访问令牌
  async exchangeCodeForToken(code: string, clientId: string, clientSecret: string, redirectUri: string) {
    const authCode = this.authorizationCodes.get(code)

    if (!authCode) {
      throw new Error('Invalid authorization code')
    }

    if (authCode.clientId !== clientId) {
      throw new Error('Client ID mismatch')
    }

    if (Date.now() > authCode.expiresAt) {
      this.authorizationCodes.delete(code)
      throw new Error('Authorization code expired')
    }

    const client = this.clients.get(clientId)
    if (client.clientSecret !== clientSecret) {
      throw new Error('Invalid client secret')
    }

    if (!client.redirectUris.includes(redirectUri)) {
      throw new Error('Invalid redirect URI')
    }

    // 生成访问令牌
    const accessToken = crypto.randomBytes(32).toString('hex')
    this.accessTokens.set(accessToken, {
      userId: authCode.userId,
      clientId,
      scopes: authCode.scopes,
      expiresAt: Date.now() + 60 * 60 * 1000  // 1小时过期
    })

    // 删除已使用的授权码
    this.authorizationCodes.delete(code)

    return {
      access_token: accessToken,
      token_type: 'Bearer',
      expires_in: 3600,
      scope: authCode.scopes.join(' ')
    }
  }

  // 验证访问令牌
  verifyAccessToken(accessToken: string) {
    const token = this.accessTokens.get(accessToken)
    if (!token) {
      return null
    }

    if (Date.now() > token.expiresAt) {
      this.accessTokens.delete(accessToken)
      return null
    }

    return token
  }

  // Google OAuth集成
  async verifyGoogleToken(idToken: string) {
    const client = new OAuth2Client(process.env.GOOGLE_CLIENT_ID)
    const ticket = await client.verifyIdToken({
      idToken,
      audience: process.env.GOOGLE_CLIENT_ID
    })
    const payload = ticket.getPayload()
    return {
      userId: payload.sub,
      email: payload.email,
      name: payload.name,
      picture: payload.picture
    }
  }
}

export const oauthService = new OAuthService()

二、数据安全

2.1 敏感数据加密

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
// 加密服务
import crypto from 'crypto'

class EncryptionService {
  private readonly ALGORITHM = 'aes-256-gcm'
  private readonly KEY_LENGTH = 32
  private readonly IV_LENGTH = 16
  private readonly AUTH_TAG_LENGTH = 16
  private key: Buffer

  constructor() {
    // 从环境变量获取加密密钥
    const keyString = process.env.ENCRYPTION_KEY || 'default-key-change-in-production'
    this.key = crypto.scryptSync(keyString, 'salt', this.KEY_LENGTH)
  }

  // 加密数据
  encrypt(plaintext: string): string {
    const iv = crypto.randomBytes(this.IV_LENGTH)
    const cipher = crypto.createCipheriv(this.ALGORITHM, this.key, iv)

    let encrypted = cipher.update(plaintext, 'utf8', 'hex')
    encrypted += cipher.final('hex')

    const authTag = cipher.getAuthTag()

    // 组合:iv + authTag + encrypted
    return iv.toString('hex') + authTag.toString('hex') + encrypted
  }

  // 解密数据
  decrypt(ciphertext: string): string {
    const iv = Buffer.from(ciphertext.slice(0, this.IV_LENGTH * 2), 'hex')
    const authTag = Buffer.from(
      ciphertext.slice(this.IV_LENGTH * 2, (this.IV_LENGTH + this.AUTH_TAG_LENGTH) * 2),
      'hex'
    )
    const encrypted = ciphertext.slice((this.IV_LENGTH + this.AUTH_TAG_LENGTH) * 2)

    const decipher = crypto.createDecipheriv(this.ALGORITHM, this.key, iv)
    decipher.setAuthTag(authTag)

    let decrypted = decipher.update(encrypted, 'hex', 'utf8')
    decrypted += decipher.final('utf8')

    return decrypted
  }

  // 哈希数据(单向)
  hash(data: string): string {
    return crypto.createHash('sha256').update(data).digest('hex')
  }

  // 生成HMAC
  generateHMAC(data: string, secret: string): string {
    return crypto.createHmac('sha256', secret).update(data).digest('hex')
  }

  // 验证HMAC
  verifyHMAC(data: string, hmac: string, secret: string): boolean {
    const computedHMAC = this.generateHMAC(data, secret)
    return crypto.timingSafeEqual(
      Buffer.from(computedHMAC, 'hex'),
      Buffer.from(hmac, 'hex')
    )
  }
}

export const encryptionService = new EncryptionService()

// 使用示例
const sensitiveData = 'user-ssn-123-45-6789'
const encrypted = encryptionService.encrypt(sensitiveData)
const decrypted = encryptionService.decrypt(encrypted)

// 数据库中存储加密数据
async function saveUserWithEncryptedData(userData: any) {
  const encrypted = encryptionService.encrypt(userData.ssn)
  await db.users.create({
    ...userData,
    ssn: encrypted,
    ssnHash: encryptionService.hash(userData.ssn)  // 用于查询
  })
}

2.2 签名验证

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// Webhook签名验证
import crypto from 'crypto'
import { Request, Response } from 'express'

function verifyWebhookSignature(req: Request, res: Response, next: NextFunction) {
  const signature = req.headers['x-webhook-signature'] as string
  const timestamp = req.headers['x-webhook-timestamp'] as string

  if (!signature || !timestamp) {
    return res.status(401).json({ error: 'Missing signature headers' })
  }

  // 检查时间戳(防重放攻击)
  const now = Date.now()
  const webhookTime = parseInt(timestamp)
  if (Math.abs(now - webhookTime) > 5 * 60 * 1000) {  // 5分钟窗口
    return res.status(401).json({ error: 'Request too old' })
  }

  // 生成预期签名
  const payload = `${timestamp}.${req.body}`
  const expectedSignature = crypto
    .createHmac('sha256', process.env.WEBHOOK_SECRET!)
    .update(payload)
    .digest('hex')

  // 安全比较签名
  const isValid = crypto.timingSafeEqual(
    Buffer.from(signature, 'hex'),
    Buffer.from(expectedSignature, 'hex')
  )

  if (!isValid) {
    return res.status(401).json({ error: 'Invalid signature' })
  }

  next()
}

三、防护策略

3.1 SQL注入防护

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 使用参数化查询
import { Pool } from 'pg'

class UserRepository {
  private pool: Pool

  constructor(pool: Pool) {
    this.pool = pool
  }

  // 安全的查询
  async findById(id: string) {
    const query = 'SELECT * FROM users WHERE id = $1'
    const result = await this.pool.query(query, [id])
    return result.rows[0]
  }

  async findByEmail(email: string) {
    const query = 'SELECT * FROM users WHERE email = $1'
    const result = await this.pool.query(query, [email])
    return result.rows[0]
  }

  // 使用查询构建器(如Knex.js)
  async findWithFilters(filters: any) {
    const query = this.pool
      .select('*')
      .from('users')

    if (filters.email) {
      query = query.where('email', filters.email)
    }

    if (filters.role) {
      query = query.where('role', filters.role)
    }

    if (filters.minAge) {
      query = query.where('age', '>=', filters.minAge)
    }

    return await query
  }

  // 使用ORM(如Prisma、TypeORM)
  async create(data: any) {
    // ORM自动处理参数化查询
    return await this.pool.query(
      'INSERT INTO users (name, email, password_hash) VALUES ($1, $2, $3) RETURNING *',
      [data.name, data.email, data.passwordHash]
    )
  }
}

3.2 XSS防护

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// 输入验证和输出编码
import validator from 'validator'
import xss from 'xss'

class SecurityMiddleware {
  // 输入验证
  static validateInput(req: Request, res: Response, next: NextFunction) {
    const { name, email, bio } = req.body

    // 验证和清理输入
    if (name && !validator.isLength(name, { min: 1, max: 100 })) {
      return res.status(400).json({ error: 'Invalid name length' })
    }

    if (email && !validator.isEmail(email)) {
      return res.status(400).json({ error: 'Invalid email format' })
    }

    if (bio && !validator.isLength(bio, { max: 500 })) {
      return res.status(400).json({ error: 'Bio too long' })
    }

    // 清理XSS
    if (name) req.body.name = xss(name)
    if (bio) req.body.bio = xss(bio)

    next()
  }

  // 设置CSP头
  static setSecurityHeaders(req: Request, res: Response, next: NextFunction) {
    // 内容安全策略
    res.setHeader('Content-Security-Policy',
      "default-src 'self'; " +
      "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.example.com; " +
      "style-src 'self' 'unsafe-inline'; " +
      "img-src 'self' data: https:; " +
      "font-src 'self'; " +
      "connect-src 'self' https://api.example.com; " +
      "frame-ancestors 'none';"
    )

    // 其他安全头
    res.setHeader('X-Content-Type-Options', 'nosniff')
    res.setHeader('X-Frame-Options', 'DENY')
    res.setHeader('X-XSS-Protection', '1; mode=block')
    res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin')
    res.setHeader('Permissions-Policy', 'geolocation=(), microphone=(), camera=()')

    next()
  }
}

3.3 CORS配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// CORS配置
import express from 'express'
import cors from 'cors'

const app = express()

// 生产环境CORS配置
const corsOptions = {
  origin: function (origin: string | undefined, callback: Function) {
    const allowedOrigins = [
      'https://example.com',
      'https://www.example.com',
      'https://app.example.com'
    ]

    // 允许无origin的请求(如移动应用、Postman)
    if (!origin) return callback(null, true)

    if (allowedOrigins.indexOf(origin) !== -1) {
      callback(null, true)
    } else {
      callback(new Error('Not allowed by CORS'))
    }
  },

  credentials: true,  // 允许携带cookie
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  exposedHeaders: ['X-Total-Count'],
  maxAge: 86400  // 预检请求缓存24小时
}

app.use(cors(corsOptions))

// 或者针对特定路由
app.options('/api/*', cors(corsOptions))
app.get('/api/data', cors(corsOptions), (req, res) => {
  res.json({ data: 'sensitive data' })
})

3.4 速率限制

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// API速率限制
import rateLimit from 'express-rate-limit'
import RedisStore from 'rate-limit-redis'
import Redis from 'ioredis'

// 不同场景的速率限制

// 1. 通用API限制
const generalLimiter = rateLimit({
  store: new RedisStore({
    client: new Redis(process.env.REDIS_URL)
  }),
  windowMs: 15 * 60 * 1000,  // 15分钟
  max: 100,  // 限制100次请求
  standardHeaders: true,
  legacyHeaders: false,
  message: 'Too many requests from this IP, please try again later.',
  handler: (req, res) => {
    res.status(429).json({
      error: 'Too many requests',
      message: 'Rate limit exceeded, please try again later.',
      retryAfter: 900  // 秒
    })
  }
})

// 2. 登录限流(更严格)
const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5,  // 15分钟内最多5次登录尝试
  skipSuccessfulRequests: true,  // 成功的请求不计入限制
  message: 'Too many login attempts, please try again later.'
})

// 3. API密钥限流
const apiKeyLimiter = rateLimit({
  store: new RedisStore({
    client: new Redis(process.env.REDIS_URL),
    prefix: 'limiter:apikey:'
  }),
  windowMs: 60 * 1000,  // 1分钟
  max: 60,  // 每分钟60次
  keyGenerator: (req) => {
    return req.headers['x-api-key'] as string
  }
})

// 应用限流
app.use('/api/', generalLimiter)
app.post('/api/auth/login', loginLimiter)
app.use('/api/v2/', apiKeyLimiter)

// 基于用户的限流
async function getUserRateLimit(userId: string) {
  const user = await db.users.findById(userId)

  // 不同用户等级有不同限制
  const limits = {
    free: { windowMs: 60 * 1000, max: 10 },
    pro: { windowMs: 60 * 1000, max: 100 },
    enterprise: { windowMs: 60 * 1000, max: 1000 }
  }

  return limits[user.plan] || limits.free
}

四、安全最佳实践

4.1 安全配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// helmet安全头配置
import helmet from 'helmet'

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      scriptSrc: ["'self'"],
      imgSrc: ["'self'", "data:", "https:"],
    }
  },
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true
  },
  noSniff: true,
  xssFilter: true,
  frameguard: { action: 'deny' }
}))

// 禁用不必要的头
app.disable('x-powered-by')

4.2 安全日志

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// 安全审计日志
class SecurityAuditLogger {
  private auditLog: any

  constructor(auditLog: any) {
    this.auditLog = auditLog
  }

  logAuthenticationAttempt(userId: string, success: boolean, ip: string) {
    this.auditLog.create({
      eventType: 'AUTH_ATTEMPT',
      userId,
      success,
      ip,
      timestamp: new Date(),
      userAgent: undefined
    })
  }

  logAuthorizationAttempt(userId: string, resource: string, action: string, success: boolean) {
    this.auditLog.create({
      eventType: 'AUTHZ_ATTEMPT',
      userId,
      resource,
      action,
      success,
      timestamp: new Date()
    })
  }

  logDataAccess(userId: string, resourceType: string, resourceId: string) {
    this.auditLog.create({
      eventType: 'DATA_ACCESS',
      userId,
      resourceType,
      resourceId,
      timestamp: new Date()
    })
  }

  logSecurityEvent(eventType: string, details: any) {
    this.auditLog.create({
      eventType,
      details,
      timestamp: new Date()
    })
  }
}

// 使用示例
const securityLogger = new SecurityAuditLogger(auditLog)

app.post('/api/auth/login', async (req, res) => {
  const { email, password } = req.body
  const ip = req.ip

  try {
    const result = await authService.login(email, password)
    securityLogger.logAuthenticationAttempt(result.user.id, true, ip)
    res.json(result)
  } catch (error) {
    securityLogger.logAuthenticationAttempt(email, false, ip)
    res.status(401).json({ error: 'Invalid credentials' })
  }
})

4.3 输入验证

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// 使用Joi进行输入验证
import Joi from 'joi'

// 验证schemas
const schemas = {
  register: Joi.object({
    name: Joi.string().min(2).max(50).required(),
    email: Joi.string().email().required(),
    password: Joi.string()
      .min(8)
      .pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/)
      .required()
      .messages({
        'string.pattern.base': 'Password must contain uppercase, lowercase, number, and special character'
      }),
    confirmPassword: Joi.string().valid(Joi.ref('password')).required(),
    age: Joi.number().integer().min(13).max(120)
  }),

  createPost: Joi.object({
    title: Joi.string().min(5).max(200).required(),
    content: Joi.string().min(10).max(10000).required(),
    tags: Joi.array().items(Joi.string().max(30)).max(10),
    published: Joi.boolean().default(false)
  }),

  updateProfile: Joi.object({
    name: Joi.string().min(2).max(50),
    bio: Joi.string().max(500),
    website: Joi.string().uri(),
    avatar: Joi.string().uri()
  })
}

// 验证中间件
function validate(schemaName: keyof typeof schemas) {
  return (req: Request, res: Response, next: NextFunction) => {
    const { error, value } = schemas[schemaName].validate(req.body, {
      abortEarly: false,
      stripUnknown: true
    })

    if (error) {
      const errors = error.details.map(detail => ({
        field: detail.path.join('.'),
        message: detail.message
      }))
      return res.status(400).json({ errors })
    }

    req.body = value
    next()
  }
}

// 使用
app.post('/api/auth/register', validate('register'), authController.register)
app.post('/api/posts', authenticate, validate('createPost'), postController.create)

总结

API安全是一个多层次的主题:

  1. 身份认证 - JWT、OAuth 2.0实现安全认证
  2. 数据加密 - 敏感数据加密存储和传输
  3. 输入验证 - 防止注入攻击和数据篡改
  4. 防护策略 - CORS、速率限制、CSP等
  5. 安全审计 - 记录和分析安全事件
  6. 最佳实践 - 遵循OWASP安全标准

API安全需要持续关注和改进,随着威胁环境的变化不断调整防护策略。

相关工具推荐