
API 安全:OWASP API Top 10
API 是现代应用的主要攻击面。以下是对最严重漏洞的防御措施。
1. 对象级授权失效(BOLA)
// 有漏洞:未检查所有权
app.get('/api/orders/:id', async (req, res) => {
const order = await Order.findById(req.params.id);
res.json(order); // 任何用户都能访问任何订单!
});
// 安全:验证用户拥有该资源
app.get('/api/orders/:id', authenticate, async (req, res) => {
const order = await Order.findOne({
_id: req.params.id,
userId: req.user.id, // 所有权检查
});
if (!order) return res.status(404).json({ error: 'Not found' });
res.json(order);
});

2. 认证失效
import rateLimit from 'express-rate-limit';
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5, // 每15分钟5次尝试
message: 'Too many login attempts, try again later',
skipSuccessfulRequests: true,
});
app.post('/api/login', loginLimiter, async (req, res) => {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user || !await bcrypt.compare(password, user.passwordHash)) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const token = jwt.sign({ id: user.id }, process.env.JWT_SECRET, { expiresIn: '15m' });
res.json({ token });
});
3. 批量赋值防护
// 有漏洞:用户可以设置 isAdmin: true
app.put('/api/users/:id', async (req, res) => {
await User.findByIdAndUpdate(req.params.id, req.body); // 危险
});
// 安全:白名单允许的字段
const ALLOWED_FIELDS = ['name', 'email', 'bio', 'avatar'];
app.put('/api/users/:id', authenticate, async (req, res) => {
const updates = Object.keys(req.body)
.filter(key => ALLOWED_FIELDS.includes(key))
.reduce((obj, key) => ({ ...obj, [key]: req.body[key] }), {});
await User.findByIdAndUpdate(req.params.id, { $set: updates });
res.json({ success: true });
});

4. SQL 注入防护
// 有漏洞
const result = await db.query(`SELECT * FROM users WHERE name = '${name}'`);
// 安全:参数化查询
const result = await db.query('SELECT * FROM users WHERE name = $1', [name]);
// 安全:使用 Zod 验证输入
import { z } from 'zod';
const searchSchema = z.object({
q: z.string().max(100).regex(/^[a-zA-Z0-9 ]+$/),
limit: z.number().int().min(1).max(100).default(20),
});
app.get('/api/search', (req, res) => {
const params = searchSchema.parse(req.query); // 无效则抛出异常
// 安全使用 params
});
5. 安全标头
import helmet from 'helmet';
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
},
},
hsts: { maxAge: 31536000, includeSubDomains: true, preload: true },
referrerPolicy: { policy: 'same-origin' },
}));
app.use(cors({
origin: ['https://myapp.com'],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
}));
app.disable('x-powered-by');

6. 按用户速率限制
import { RateLimiterRedis } from 'rate-limiter-flexible';
const apiLimiter = new RateLimiterRedis({
storeClient: redisClient,
points: 100, // 请求数
duration: 60, // 每60秒每用户
blockDuration: 60,
});
const rateLimitMiddleware = async (req: Request, res: Response, next: NextFunction) => {
try {
await apiLimiter.consume(req.user?.id || req.ip);
next();
} catch {
res.status(429).json({ error: 'Too many requests', retryAfter: 60 });
}
};
7. JWT 最佳实践
// 短生命周期访问令牌 + 刷新令牌
const accessToken = jwt.sign(
{ id: user.id, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: '15m', issuer: 'myapi.com', algorithm: 'HS256' }
);
const refreshToken = jwt.sign(
{ id: user.id, type: 'refresh' },
process.env.REFRESH_SECRET,
{ expiresIn: '7d' }
);
// 在数据库中存储刷新令牌的哈希以便撤销
await db.refreshTokens.create({
userId: user.id,
tokenHash: crypto.createHash('sha256').update(refreshToken).digest('hex'),
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
});
安全测试清单
- 测试 BOLA:用户 A 能否访问用户 B 的资源?
- 测试批量赋值:用户能否设置管理员字段?
- 测试注入:SQL、NoSQL、命令注入
- 测试速率限制:能否暴力破解登录?
- 检查安全标头:securityheaders.com
- 测试 JWT:过期、错误签名、算法混淆
- 运行 OWASP ZAP 或 Burp Suite 自动化扫描
总结
API 安全是分层防御:
- 始终检查对象所有权(BOLA)
- 对认证端点进行速率限制
- 白名单允许更新的字段
- 仅使用参数化查询
- 使用 Helmet 设置安全标头
- 使用短生命周期的 JWT 和刷新令牌
- 使用模式库验证所有输入