API 安全 #
概述 #
API 安全是 API 设计中最重要的方面之一。一个不安全的 API 可能导致数据泄露、服务滥用、经济损失等严重后果。
text
┌─────────────────────────────────────────────────────────────┐
│ API 安全重要性 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 安全威胁: │
│ - 数据泄露:敏感信息被窃取 │
│ - 未授权访问:非法获取资源 │
│ - 服务滥用:恶意调用消耗资源 │
│ - 注入攻击:SQL 注入、XSS 等 │
│ - 中间人攻击:通信被窃听 │
│ │
│ 安全目标: │
│ - 机密性:数据不被未授权者访问 │
│ - 完整性:数据不被篡改 │
│ - 可用性:服务正常运行 │
│ - 可追溯:操作可审计 │
│ │
└─────────────────────────────────────────────────────────────┘
认证(Authentication) #
认证方式对比 #
text
┌─────────────────────────────────────────────────────────────┐
│ 认证方式对比 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 方式 适用场景 优缺点 │
│ ───────────────────────────────────────────────────────── │
│ API Key 服务间调用 简单但安全性较低 │
│ Basic Auth 简单场景 不推荐,安全性低 │
│ JWT 用户认证 推荐,无状态 │
│ OAuth 2.0 第三方授权 推荐,功能完善 │
│ Session Cookie 传统 Web 应用 有状态,不适合 API │
│ │
└─────────────────────────────────────────────────────────────┘
API Key 认证 #
text
┌─────────────────────────────────────────────────────────────┐
│ API Key 认证 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 使用方式: │
│ │
│ 方式一:请求头 │
│ GET /users HTTP/1.1 │
│ X-API-Key: your-api-key-here │
│ │
│ 方式二:查询参数 │
│ GET /users?api_key=your-api-key-here │
│ │
│ 方式三:Authorization Header │
│ Authorization: ApiKey your-api-key-here │
│ │
│ 最佳实践: │
│ ✅ 使用请求头传递 │
│ ✅ 每个 API Key 关联特定权限 │
│ ✅ 设置 API Key 过期时间 │
│ ✅ 支持 API Key 撤销 │
│ ✅ 记录 API Key 使用日志 │
│ │
│ 注意事项: │
│ ❌ 不要将 API Key 嵌入前端代码 │
│ ❌ 不要在日志中记录 API Key │
│ ❌ 不要使用 URL 参数传递(容易被记录) │
│ │
└─────────────────────────────────────────────────────────────┘
JWT 认证 #
text
┌─────────────────────────────────────────────────────────────┐
│ JWT 认证 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 认证流程: │
│ │
│ 1. 用户登录 │
│ POST /auth/login │
│ { "username": "user", "password": "pass" } │
│ │
│ 2. 服务端验证并返回 JWT │
│ { "token": "eyJhbGciOiJIUzI1NiIs..." } │
│ │
│ 3. 后续请求携带 JWT │
│ GET /users/123 │
│ Authorization: Bearer eyJhbGciOiJIUzI1NiIs... │
│ │
│ 4. 服务端验证 JWT │
│ - 验证签名 │
│ - 验证过期时间 │
│ - 解析用户信息 │
│ │
│ 最佳实践: │
│ ✅ 使用 HTTPS 传输 │
│ ✅ 设置合理的过期时间 │
│ ✅ 实现刷新 Token 机制 │
│ ✅ 不在 JWT 中存储敏感信息 │
│ ✅ 使用强密钥签名 │
│ │
└─────────────────────────────────────────────────────────────┘
OAuth 2.0 认证 #
text
┌─────────────────────────────────────────────────────────────┐
│ OAuth 2.0 认证 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 授权码模式流程: │
│ │
│ 1. 引导用户到授权页面 │
│ GET /authorize? │
│ response_type=code& │
│ client_id=CLIENT_ID& │
│ redirect_uri=REDIRECT_URI& │
│ scope=read write │
│ │
│ 2. 用户授权后重定向 │
│ REDIRECT_URI?code=AUTHORIZATION_CODE │
│ │
│ 3. 用授权码换取 Token │
│ POST /token │
│ { │
│ "grant_type": "authorization_code", │
│ "code": "AUTHORIZATION_CODE", │
│ "client_id": "CLIENT_ID", │
│ "client_secret": "CLIENT_SECRET" │
│ } │
│ │
│ 4. 获取 Access Token │
│ { │
│ "access_token": "...", │
│ "token_type": "Bearer", │
│ "expires_in": 3600, │
│ "refresh_token": "..." │
│ } │
│ │
└─────────────────────────────────────────────────────────────┘
授权(Authorization) #
RBAC 权限模型 #
text
┌─────────────────────────────────────────────────────────────┐
│ RBAC 权限模型 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 用户(User)→ 角色(Role)→ 权限(Permission) │
│ │
│ 示例: │
│ │
│ 用户 角色 权限 │
│ ───────────────────────────────────────────────────────── │
│ 张三 → 管理员 → 用户管理、订单管理 │
│ 李四 → 运营 → 订单管理、商品查看 │
│ 王五 → 普通用户 → 个人信息查看 │
│ │
│ 权限定义: │
│ - users:read 读取用户信息 │
│ - users:write 创建/更新用户 │
│ - users:delete 删除用户 │
│ - orders:read 读取订单 │
│ - orders:write 创建/更新订单 │
│ │
│ 实现: │
│ // 中间件检查权限 │
│ function checkPermission(permission) { │
│ return (req, res, next) => { │
│ const userPermissions = req.user.permissions; │
│ if (userPermissions.includes(permission)) { │
│ next(); │
│ } else { │
│ res.status(403).json({ error: 'Forbidden' }); │
│ } │
│ }; │
│ } │
│ │
│ // 使用 │
│ app.delete('/users/:id', │
│ authenticate, │
│ checkPermission('users:delete'), │
│ deleteUser │
│ ); │
│ │
└─────────────────────────────────────────────────────────────┘
资源级权限控制 #
text
┌─────────────────────────────────────────────────────────────┐
│ 资源级权限控制 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 场景:用户只能访问自己的订单 │
│ │
│ 实现: │
│ GET /users/:userId/orders │
│ │
│ // 中间件检查资源所有权 │
│ function checkResourceOwnership(req, res, next) { │
│ const requestedUserId = req.params.userId; │
│ const currentUserId = req.user.id; │
│ │
│ // 管理员可以访问所有资源 │
│ if (req.user.role === 'admin') { │
│ return next(); │
│ } │
│ │
│ // 普通用户只能访问自己的资源 │
│ if (requestedUserId !== currentUserId) { │
│ return res.status(403).json({ │
│ error: 'You can only access your own resources' │
│ }); │
│ } │
│ │
│ next(); │
│ } │
│ │
└─────────────────────────────────────────────────────────────┘
HTTPS 与传输安全 #
HTTPS 配置 #
text
┌─────────────────────────────────────────────────────────────┐
│ HTTPS 配置 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 为什么必须使用 HTTPS: │
│ - 防止中间人攻击 │
│ - 保护敏感数据传输 │
│ - 防止 Token 被窃取 │
│ - 满足合规要求 │
│ │
│ 最佳实践: │
│ ✅ 强制使用 HTTPS │
│ ✅ 使用 TLS 1.2 或更高版本 │
│ ✅ 配置 HSTS 头 │
│ ✅ 使用强加密套件 │
│ ✅ 定期更新证书 │
│ │
│ HSTS 配置: │
│ Strict-Transport-Security: max-age=31536000; │
│ includeSubDomains; preload │
│ │
│ HTTP 重定向到 HTTPS: │
│ // Express.js │
│ app.use((req, res, next) => { │
│ if (!req.secure) { │
│ return res.redirect(301, `https://${req.headers.host}${req.url}`); │
│ } │
│ next(); │
│ }); │
│ │
└─────────────────────────────────────────────────────────────┘
速率限制 #
速率限制策略 #
text
┌─────────────────────────────────────────────────────────────┐
│ 速率限制策略 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 限制维度: │
│ - IP 地址 │
│ - 用户 ID │
│ - API Key │
│ - 端点 │
│ │
│ 限制算法: │
│ │
│ 1. 固定窗口 │
│ - 每分钟最多 100 次请求 │
│ - 简单但可能有突发 │
│ │
│ 2. 滑动窗口 │
│ - 任意 1 分钟内最多 100 次请求 │
│ - 更精确但计算复杂 │
│ │
│ 3. 令牌桶 │
│ - 桶容量 100,每秒补充 10 个 │
│ - 允许一定突发 │
│ │
│ 响应头: │
│ X-RateLimit-Limit: 100 │
│ X-RateLimit-Remaining: 95 │
│ X-RateLimit-Reset: 1640995200 │
│ │
│ 超限响应: │
│ HTTP/1.1 429 Too Many Requests │
│ Retry-After: 60 │
│ │
│ { │
│ "error": { │
│ "code": "RATE_LIMIT_EXCEEDED", │
│ "message": "Rate limit exceeded", │
│ "retryAfter": 60 │
│ } │
│ } │
│ │
└─────────────────────────────────────────────────────────────┘
实现示例 #
text
┌─────────────────────────────────────────────────────────────┐
│ 速率限制实现 │
├─────────────────────────────────────────────────────────────┤
│ │
│ // Express.js + express-rate-limit │
│ const rateLimit = require('express-rate-limit'); │
│ │
│ // 全局限制 │
│ const globalLimiter = rateLimit({ │
│ windowMs: 15 * 60 * 1000, // 15 分钟 │
│ max: 100, // 最多 100 次请求 │
│ message: { │
│ error: { │
│ code: 'RATE_LIMIT_EXCEEDED', │
│ message: 'Too many requests' │
│ } │
│ } │
│ }); │
│ │
│ app.use(globalLimiter); │
│ │
│ // 端点级别限制 │
│ const loginLimiter = rateLimit({ │
│ windowMs: 60 * 60 * 1000, // 1 小时 │
│ max: 5, // 最多 5 次登录尝试 │
│ message: { │
│ error: { │
│ code: 'TOO_MANY_LOGIN_ATTEMPTS', │
│ message: 'Too many login attempts' │
│ } │
│ } │
│ }); │
│ │
│ app.post('/auth/login', loginLimiter, loginHandler); │
│ │
└─────────────────────────────────────────────────────────────┘
输入验证 #
验证原则 #
text
┌─────────────────────────────────────────────────────────────┐
│ 输入验证原则 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 永远不要信任客户端输入 │
│ - 所有输入都必须验证 │
│ - 验证在服务端进行 │
│ │
│ 2. 白名单优于黑名单 │
│ - 定义允许的输入格式 │
│ - 而非过滤不允许的内容 │
│ │
│ 3. 尽早验证,尽早失败 │
│ - 在处理请求前验证 │
│ - 返回明确的错误信息 │
│ │
│ 4. 参数化查询 │
│ - 防止 SQL 注入 │
│ - 使用 ORM 或参数化语句 │
│ │
└─────────────────────────────────────────────────────────────┘
验证示例 #
text
┌─────────────────────────────────────────────────────────────┐
│ 输入验证示例 │
├─────────────────────────────────────────────────────────────┤
│ │
│ // 使用 Joi 验证 │
│ const Joi = require('joi'); │
│ │
│ const userSchema = 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)/) │
│ .required(), │
│ age: Joi.number().integer().min(0).max(150), │
│ role: Joi.string().valid('user', 'admin').default('user')│
│ }); │
│ │
│ // 中间件验证 │
│ function validateUser(req, res, next) { │
│ const { error, value } = userSchema.validate(req.body); │
│ if (error) { │
│ return res.status(400).json({ │
│ error: { │
│ code: 'VALIDATION_ERROR', │
│ message: 'Invalid input', │
│ details: error.details │
│ } │
│ }); │
│ } │
│ req.body = value; // 使用验证后的值 │
│ next(); │
│ } │
│ │
│ // 防止 SQL 注入 │
│ // ❌ 错误:字符串拼接 │
│ const query = `SELECT * FROM users WHERE id = ${userId}`; │
│ │
│ // ✅ 正确:参数化查询 │
│ const query = 'SELECT * FROM users WHERE id = ?'; │
│ db.query(query, [userId]); │
│ │
│ // ✅ 正确:使用 ORM │
│ User.findByPk(userId); │
│ │
└─────────────────────────────────────────────────────────────┘
CORS 配置 #
CORS 配置 #
text
┌─────────────────────────────────────────────────────────────┐
│ CORS 配置 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 什么是 CORS: │
│ Cross-Origin Resource Sharing │
│ 跨域资源共享机制 │
│ │
│ 预检请求: │
│ OPTIONS /users HTTP/1.1 │
│ Origin: https://client.example.com │
│ Access-Control-Request-Method: POST │
│ Access-Control-Request-Headers: Content-Type │
│ │
│ 预检响应: │
│ HTTP/1.1 200 OK │
│ Access-Control-Allow-Origin: https://client.example.com │
│ Access-Control-Allow-Methods: GET, POST, PUT, DELETE │
│ Access-Control-Allow-Headers: Content-Type, Authorization │
│ Access-Control-Max-Age: 86400 │
│ │
│ 最佳实践: │
│ ✅ 明确指定允许的 Origin │
│ ✅ 不要使用 Access-Control-Allow-Origin: * │
│ ✅ 限制允许的方法和头部 │
│ ✡ 设置合理的缓存时间 │
│ ✅ 凭证请求需要更严格的配置 │
│ │
│ // Express.js CORS 配置 │
│ const cors = require('cors'); │
│ │
│ const corsOptions = { │
│ origin: ['https://client.example.com'], │
│ methods: ['GET', 'POST', 'PUT', 'DELETE'], │
│ allowedHeaders: ['Content-Type', 'Authorization'], │
│ credentials: true, │
│ maxAge: 86400 │
│ }; │
│ │
│ app.use(cors(corsOptions)); │
│ │
└─────────────────────────────────────────────────────────────┘
安全响应头 #
text
┌─────────────────────────────────────────────────────────────┐
│ 安全响应头 │
├─────────────────────────────────────────────────────────────┤
│ │
│ // Express.js 使用 helmet │
│ const helmet = require('helmet'); │
│ app.use(helmet()); │
│ │
│ 安全响应头列表: │
│ │
│ Strict-Transport-Security: max-age=31536000 │
│ 强制使用 HTTPS │
│ │
│ X-Content-Type-Options: nosniff │
│ 防止 MIME 类型嗅探 │
│ │
│ X-Frame-Options: DENY │
│ 防止点击劫持 │
│ │
│ X-XSS-Protection: 1; mode=block │
│ XSS 保护(已废弃,推荐 CSP) │
│ │
│ Content-Security-Policy: default-src 'self' │
│ 内容安全策略 │
│ │
│ Cache-Control: no-store │
│ 敏感数据不缓存 │
│ │
│ X-Request-ID: uuid │
│ 请求追踪 │
│ │
└─────────────────────────────────────────────────────────────┘
安全检查清单 #
text
□ 认证与授权
□ 使用安全的认证方式(JWT/OAuth)
□ 实现权限控制
□ 验证资源所有权
□ 传输安全
□ 强制使用 HTTPS
□ 配置 HSTS
□ 使用 TLS 1.2+
□ 速率限制
□ 实现全局速率限制
□ 敏感端点加强限制
□ 返回限制信息
□ 输入验证
□ 验证所有输入
□ 使用参数化查询
□ 防止注入攻击
□ CORS 配置
□ 明确指定允许的 Origin
□ 限制方法和头部
□ 正确处理凭证请求
□ 安全响应头
□ 配置安全响应头
□ 敏感数据不缓存
□ 实现请求追踪
□ 日志与监控
□ 记录安全事件
□ 监控异常请求
□ 定期审计
下一步 #
现在你已经了解了 API 安全,接下来学习 分页与过滤,深入了解如何处理大量数据!
最后更新:2026-03-29