api-security-best-practices
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAPI Security Best Practices
API安全最佳实践
Overview
概述
Guide developers in building secure APIs by implementing authentication, authorization, input validation, rate limiting, and protection against common vulnerabilities. This skill covers security patterns for REST, GraphQL, and WebSocket APIs.
指导开发者通过实现身份验证、授权、输入验证、速率限制以及针对常见漏洞的防护来构建安全的API。本技能涵盖REST、GraphQL和WebSocket API的安全模式。
When to Use This Skill
何时使用本技能
- Use when designing new API endpoints
- Use when securing existing APIs
- Use when implementing authentication and authorization
- Use when protecting against API attacks (injection, DDoS, etc.)
- Use when conducting API security reviews
- Use when preparing for security audits
- Use when implementing rate limiting and throttling
- Use when handling sensitive data in APIs
- 设计新的API端点时使用
- 加固现有API时使用
- 实现身份验证和授权机制时使用
- 抵御API攻击(注入、DDoS等)时使用
- 开展API安全评审时使用
- 准备安全审计时使用
- 实现速率限制和流量控制时使用
- 在API中处理敏感数据时使用
How It Works
工作流程
Step 1: Authentication & Authorization
步骤1:身份验证与授权
I'll help you implement secure authentication:
- Choose authentication method (JWT, OAuth 2.0, API keys)
- Implement token-based authentication
- Set up role-based access control (RBAC)
- Secure session management
- Implement multi-factor authentication (MFA)
我将帮助你实现安全的身份验证机制:
- 选择身份验证方式(JWT、OAuth 2.0、API密钥)
- 实现基于令牌的身份验证
- 配置基于角色的访问控制(RBAC)
- 安全的会话管理
- 实现多因素身份验证(MFA)
Step 2: Input Validation & Sanitization
步骤2:输入验证与清理
Protect against injection attacks:
- Validate all input data
- Sanitize user inputs
- Use parameterized queries
- Implement request schema validation
- Prevent SQL injection, XSS, and command injection
抵御注入攻击:
- 验证所有输入数据
- 清理用户输入
- 使用参数化查询
- 实现请求 schema 验证
- 防止SQL注入、XSS和命令注入
Step 3: Rate Limiting & Throttling
步骤3:速率限制与流量控制
Prevent abuse and DDoS attacks:
- Implement rate limiting per user/IP
- Set up API throttling
- Configure request quotas
- Handle rate limit errors gracefully
- Monitor for suspicious activity
防止滥用和DDoS攻击:
- 按用户/IP实现速率限制
- 配置API流量控制
- 设置请求配额
- 优雅处理速率限制错误
- 监控可疑活动
Step 4: Data Protection
步骤4:数据保护
Secure sensitive data:
- Encrypt data in transit (HTTPS/TLS)
- Encrypt sensitive data at rest
- Implement proper error handling (no data leaks)
- Sanitize error messages
- Use secure headers
保护敏感数据:
- 加密传输中的数据(HTTPS/TLS)
- 加密静态存储的敏感数据
- 实现恰当的错误处理(避免数据泄露)
- 清理错误信息
- 使用安全HTTP头
Step 5: API Security Testing
步骤5:API安全测试
Verify security implementation:
- Test authentication and authorization
- Perform penetration testing
- Check for common vulnerabilities (OWASP API Top 10)
- Validate input handling
- Test rate limiting
验证安全实现:
- 测试身份验证和授权机制
- 执行渗透测试
- 检查常见漏洞(OWASP API Top 10)
- 验证输入处理逻辑
- 测试速率限制功能
Examples
示例
Example 1: Implementing JWT Authentication
示例1:实现JWT身份验证
markdown
undefinedmarkdown
undefinedSecure JWT Authentication Implementation
安全的JWT身份验证实现
Authentication Flow
身份验证流程
- User logs in with credentials
- Server validates credentials
- Server generates JWT token
- Client stores token securely
- Client sends token with each request
- Server validates token
- 用户使用凭证登录
- 服务器验证凭证
- 服务器生成JWT令牌
- 客户端安全存储令牌
- 客户端在每个请求中携带令牌
- 服务器验证令牌
Implementation
实现代码
1. Generate Secure JWT Tokens
1. 生成安全的JWT令牌
```javascript
// auth.js
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
// Login endpoint
app.post('/api/auth/login', async (req, res) => {
try {
const { email, password } = req.body;
// Validate input
if (!email || !password) {
return res.status(400).json({
error: 'Email and password are required'
});
}
// Find user
const user = await db.user.findUnique({
where: { email }
});
if (!user) {
// Don't reveal if user exists
return res.status(401).json({
error: 'Invalid credentials'
});
}
// Verify password
const validPassword = await bcrypt.compare(
password,
user.passwordHash
);
if (!validPassword) {
return res.status(401).json({
error: 'Invalid credentials'
});
}
// Generate JWT token
const token = jwt.sign(
{
userId: user.id,
email: user.email,
role: user.role
},
process.env.JWT_SECRET,
{
expiresIn: '1h',
issuer: 'your-app',
audience: 'your-app-users'
}
);
// Generate refresh token
const refreshToken = jwt.sign(
{ userId: user.id },
process.env.JWT_REFRESH_SECRET,
{ expiresIn: '7d' }
);
// Store refresh token in database
await db.refreshToken.create({
data: {
token: refreshToken,
userId: user.id,
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
}
});
res.json({
token,
refreshToken,
expiresIn: 3600
});} catch (error) {
console.error('Login error:', error);
res.status(500).json({
error: 'An error occurred during login'
});
}
});
```
javascript
// auth.js
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
// 登录端点
app.post('/api/auth/login', async (req, res) => {
try {
const { email, password } = req.body;
// 验证输入
if (!email || !password) {
return res.status(400).json({
error: 'Email and password are required'
});
}
// 查询用户
const user = await db.user.findUnique({
where: { email }
});
if (!user) {
// 不泄露用户是否存在
return res.status(401).json({
error: 'Invalid credentials'
});
}
// 验证密码
const validPassword = await bcrypt.compare(
password,
user.passwordHash
);
if (!validPassword) {
return res.status(401).json({
error: 'Invalid credentials'
});
}
// 生成JWT令牌
const token = jwt.sign(
{
userId: user.id,
email: user.email,
role: user.role
},
process.env.JWT_SECRET,
{
expiresIn: '1h',
issuer: 'your-app',
audience: 'your-app-users'
}
);
// 生成刷新令牌
const refreshToken = jwt.sign(
{ userId: user.id },
process.env.JWT_REFRESH_SECRET,
{ expiresIn: '7d' }
);
// 将刷新令牌存储到数据库
await db.refreshToken.create({
data: {
token: refreshToken,
userId: user.id,
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
}
});
res.json({
token,
refreshToken,
expiresIn: 3600
});
} catch (error) {
console.error('Login error:', error);
res.status(500).json({
error: 'An error occurred during login'
});
}
});2. Verify JWT Tokens (Middleware)
2. 验证JWT令牌(中间件)
```javascript
// middleware/auth.js
const jwt = require('jsonwebtoken');
function authenticateToken(req, res, next) {
// Get token from header
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
if (!token) {
return res.status(401).json({
error: 'Access token required'
});
}
// Verify token
jwt.verify(
token,
process.env.JWT_SECRET,
{
issuer: 'your-app',
audience: 'your-app-users'
},
(err, user) => {
if (err) {
if (err.name === 'TokenExpiredError') {
return res.status(401).json({
error: 'Token expired'
});
}
return res.status(403).json({
error: 'Invalid token'
});
}
// Attach user to request
req.user = user;
next();
});
}
module.exports = { authenticateToken };
```
javascript
// middleware/auth.js
const jwt = require('jsonwebtoken');
function authenticateToken(req, res, next) {
// 从请求头获取令牌
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
if (!token) {
return res.status(401).json({
error: 'Access token required'
});
}
// 验证令牌
jwt.verify(
token,
process.env.JWT_SECRET,
{
issuer: 'your-app',
audience: 'your-app-users'
},
(err, user) => {
if (err) {
if (err.name === 'TokenExpiredError') {
return res.status(401).json({
error: 'Token expired'
});
}
return res.status(403).json({
error: 'Invalid token'
});
}
// 将用户信息附加到请求对象
req.user = user;
next();
}
);
}
module.exports = { authenticateToken };3. Protect Routes
3. 保护路由
```javascript
const { authenticateToken } = require('./middleware/auth');
// Protected route
app.get('/api/user/profile', authenticateToken, async (req, res) => {
try {
const user = await db.user.findUnique({
where: { id: req.user.userId },
select: {
id: true,
email: true,
name: true,
// Don't return passwordHash
}
});
res.json(user);} catch (error) {
res.status(500).json({ error: 'Server error' });
}
});
```
javascript
const { authenticateToken } = require('./middleware/auth');
// 受保护的路由
app.get('/api/user/profile', authenticateToken, async (req, res) => {
try {
const user = await db.user.findUnique({
where: { id: req.user.userId },
select: {
id: true,
email: true,
name: true,
// 不返回密码哈希
}
});
res.json(user);
} catch (error) {
res.status(500).json({ error: 'Server error' });
}
});4. Implement Token Refresh
4. 实现令牌刷新
```javascript
app.post('/api/auth/refresh', async (req, res) => {
const { refreshToken } = req.body;
if (!refreshToken) {
return res.status(401).json({
error: 'Refresh token required'
});
}
try {
// Verify refresh token
const decoded = jwt.verify(
refreshToken,
process.env.JWT_REFRESH_SECRET
);
// Check if refresh token exists in database
const storedToken = await db.refreshToken.findFirst({
where: {
token: refreshToken,
userId: decoded.userId,
expiresAt: { gt: new Date() }
}
});
if (!storedToken) {
return res.status(403).json({
error: 'Invalid refresh token'
});
}
// Generate new access token
const user = await db.user.findUnique({
where: { id: decoded.userId }
});
const newToken = jwt.sign(
{
userId: user.id,
email: user.email,
role: user.role
},
process.env.JWT_SECRET,
{ expiresIn: '1h' }
);
res.json({
token: newToken,
expiresIn: 3600
});} catch (error) {
res.status(403).json({
error: 'Invalid refresh token'
});
}
});
```
javascript
app.post('/api/auth/refresh', async (req, res) => {
const { refreshToken } = req.body;
if (!refreshToken) {
return res.status(401).json({
error: 'Refresh token required'
});
}
try {
// 验证刷新令牌
const decoded = jwt.verify(
refreshToken,
process.env.JWT_REFRESH_SECRET
);
// 检查刷新令牌是否存在于数据库
const storedToken = await db.refreshToken.findFirst({
where: {
token: refreshToken,
userId: decoded.userId,
expiresAt: { gt: new Date() }
}
});
if (!storedToken) {
return res.status(403).json({
error: 'Invalid refresh token'
});
}
// 生成新的访问令牌
const user = await db.user.findUnique({
where: { id: decoded.userId }
});
const newToken = jwt.sign(
{
userId: user.id,
email: user.email,
role: user.role
},
process.env.JWT_SECRET,
{ expiresIn: '1h' }
);
res.json({
token: newToken,
expiresIn: 3600
});
} catch (error) {
res.status(403).json({
error: 'Invalid refresh token'
});
}
});Security Best Practices
安全最佳实践
- ✅ Use strong JWT secrets (256-bit minimum)
- ✅ Set short expiration times (1 hour for access tokens)
- ✅ Implement refresh tokens for long-lived sessions
- ✅ Store refresh tokens in database (can be revoked)
- ✅ Use HTTPS only
- ✅ Don't store sensitive data in JWT payload
- ✅ Validate token issuer and audience
- ✅ Implement token blacklisting for logout
undefined- ✅ 使用强JWT密钥(至少256位)
- ✅ 设置短过期时间(访问令牌1小时)
- ✅ 为长会话实现刷新令牌机制
- ✅ 将刷新令牌存储在数据库(可撤销)
- ✅ 仅使用HTTPS
- ✅ 不要在JWT负载中存储敏感数据
- ✅ 验证令牌的签发者和受众
- ✅ 为登出实现令牌黑名单
undefinedExample 2: Input Validation and SQL Injection Prevention
示例2:输入验证与SQL注入防护
markdown
undefinedmarkdown
undefinedPreventing SQL Injection and Input Validation
防止SQL注入与输入验证
The Problem
问题场景
❌ Vulnerable Code:
```javascript
// NEVER DO THIS - SQL Injection vulnerability
app.get('/api/users/:id', async (req, res) => {
const userId = req.params.id;
// Dangerous: User input directly in query
const query = `SELECT * FROM users WHERE id = '${userId}'`;
const user = await db.query(query);
res.json(user);
});
// Attack example:
// GET /api/users/1' OR '1'='1
// Returns all users!
```
❌ 存在漏洞的代码:
javascript
// 绝对不要这么做 - 存在SQL注入漏洞
app.get('/api/users/:id', async (req, res) => {
const userId = req.params.id;
// 危险:用户输入直接拼接到查询语句中
const query = `SELECT * FROM users WHERE id = '${userId}'`;
const user = await db.query(query);
res.json(user);
});
// 攻击示例:
// GET /api/users/1' OR '1'='1
// 返回所有用户!The Solution
解决方案
1. Use Parameterized Queries
1. 使用参数化查询
```javascript
// ✅ Safe: Parameterized query
app.get('/api/users/:id', async (req, res) => {
const userId = req.params.id;
// Validate input first
if (!userId || !/^\d+$/.test(userId)) {
return res.status(400).json({
error: 'Invalid user ID'
});
}
// Use parameterized query
const user = await db.query(
'SELECT id, email, name FROM users WHERE id = $1',
[userId]
);
if (!user) {
return res.status(404).json({
error: 'User not found'
});
}
res.json(user);
});
```
javascript
// ✅ 安全:参数化查询
app.get('/api/users/:id', async (req, res) => {
const userId = req.params.id;
// 先验证输入
if (!userId || !/^\d+$/.test(userId)) {
return res.status(400).json({
error: 'Invalid user ID'
});
}
// 使用参数化查询
const user = await db.query(
'SELECT id, email, name FROM users WHERE id = $1',
[userId]
);
if (!user) {
return res.status(404).json({
error: 'User not found'
});
}
res.json(user);
});2. Use ORM with Proper Escaping
2. 使用ORM并正确转义
```javascript
// ✅ Safe: Using Prisma ORM
app.get('/api/users/:id', async (req, res) => {
const userId = parseInt(req.params.id);
if (isNaN(userId)) {
return res.status(400).json({
error: 'Invalid user ID'
});
}
const user = await prisma.user.findUnique({
where: { id: userId },
select: {
id: true,
email: true,
name: true,
// Don't select sensitive fields
}
});
if (!user) {
return res.status(404).json({
error: 'User not found'
});
}
res.json(user);
});
```
javascript
// ✅ 安全:使用Prisma ORM
app.get('/api/users/:id', async (req, res) => {
const userId = parseInt(req.params.id);
if (isNaN(userId)) {
return res.status(400).json({
error: 'Invalid user ID'
});
}
const user = await prisma.user.findUnique({
where: { id: userId },
select: {
id: true,
email: true,
name: true,
// 不选择敏感字段
}
});
if (!user) {
return res.status(404).json({
error: 'User not found'
});
}
res.json(user);
});3. Implement Request Validation with Zod
3. 使用Zod实现请求验证
```javascript
const { z } = require('zod');
// Define validation schema
const createUserSchema = z.object({
email: z.string().email('Invalid email format'),
password: z.string()
.min(8, 'Password must be at least 8 characters')
.regex(/[A-Z]/, 'Password must contain uppercase letter')
.regex(/[a-z]/, 'Password must contain lowercase letter')
.regex(/[0-9]/, 'Password must contain number'),
name: z.string()
.min(2, 'Name must be at least 2 characters')
.max(100, 'Name too long'),
age: z.number()
.int('Age must be an integer')
.min(18, 'Must be 18 or older')
.max(120, 'Invalid age')
.optional()
});
// Validation middleware
function validateRequest(schema) {
return (req, res, next) => {
try {
schema.parse(req.body);
next();
} catch (error) {
res.status(400).json({
error: 'Validation failed',
details: error.errors
});
}
};
}
// Use validation
app.post('/api/users',
validateRequest(createUserSchema),
async (req, res) => {
// Input is validated at this point
const { email, password, name, age } = req.body;
// Hash password
const passwordHash = await bcrypt.hash(password, 10);
// Create user
const user = await prisma.user.create({
data: {
email,
passwordHash,
name,
age
}
});
// Don't return password hash
const { passwordHash: _, ...userWithoutPassword } = user;
res.status(201).json(userWithoutPassword);}
);
```
javascript
const { z } = require('zod');
// 定义验证schema
const createUserSchema = z.object({
email: z.string().email('Invalid email format'),
password: z.string()
.min(8, 'Password must be at least 8 characters')
.regex(/[A-Z]/, 'Password must contain uppercase letter')
.regex(/[a-z]/, 'Password must contain lowercase letter')
.regex(/[0-9]/, 'Password must contain number'),
name: z.string()
.min(2, 'Name must be at least 2 characters')
.max(100, 'Name too long'),
age: z.number()
.int('Age must be an integer')
.min(18, 'Must be 18 or older')
.max(120, 'Invalid age')
.optional()
});
// 验证中间件
function validateRequest(schema) {
return (req, res, next) => {
try {
schema.parse(req.body);
next();
} catch (error) {
res.status(400).json({
error: 'Validation failed',
details: error.errors
});
}
};
}
// 使用验证中间件
app.post('/api/users',
validateRequest(createUserSchema),
async (req, res) => {
// 此时输入已通过验证
const { email, password, name, age } = req.body;
// 哈希密码
const passwordHash = await bcrypt.hash(password, 10);
// 创建用户
const user = await prisma.user.create({
data: {
email,
passwordHash,
name,
age
}
});
// 不返回密码哈希
const { passwordHash: _, ...userWithoutPassword } = user;
res.status(201).json(userWithoutPassword);
}
);4. Sanitize Output to Prevent XSS
4. 清理输出以防止XSS
```javascript
const DOMPurify = require('isomorphic-dompurify');
app.post('/api/comments', authenticateToken, async (req, res) => {
const { content } = req.body;
// Validate
if (!content || content.length > 1000) {
return res.status(400).json({
error: 'Invalid comment content'
});
}
// Sanitize HTML to prevent XSS
const sanitizedContent = DOMPurify.sanitize(content, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
ALLOWED_ATTR: ['href']
});
const comment = await prisma.comment.create({
data: {
content: sanitizedContent,
userId: req.user.userId
}
});
res.status(201).json(comment);
});
```
javascript
const DOMPurify = require('isomorphic-dompurify');
app.post('/api/comments', authenticateToken, async (req, res) => {
const { content } = req.body;
// 验证输入
if (!content || content.length > 1000) {
return res.status(400).json({
error: 'Invalid comment content'
});
}
// 清理HTML以防止XSS
const sanitizedContent = DOMPurify.sanitize(content, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
ALLOWED_ATTR: ['href']
});
const comment = await prisma.comment.create({
data: {
content: sanitizedContent,
userId: req.user.userId
}
});
res.status(201).json(comment);
});Validation Checklist
验证检查清单
- Validate all user inputs
- Use parameterized queries or ORM
- Validate data types (string, number, email, etc.)
- Validate data ranges (min/max length, value ranges)
- Sanitize HTML content
- Escape special characters
- Validate file uploads (type, size, content)
- Use allowlists, not blocklists
undefined- 验证所有用户输入
- 使用参数化查询或ORM
- 验证数据类型(字符串、数字、邮箱等)
- 验证数据范围(最小/最大长度、值范围)
- 清理HTML内容
- 转义特殊字符
- 验证文件上传(类型、大小、内容)
- 使用白名单,而非黑名单
undefinedExample 3: Rate Limiting and DDoS Protection
示例3:速率限制与DDoS防护
markdown
undefinedmarkdown
undefinedImplementing Rate Limiting
实现速率限制
Why Rate Limiting?
为什么需要速率限制?
- Prevent brute force attacks
- Protect against DDoS
- Prevent API abuse
- Ensure fair usage
- Reduce server costs
- 防止暴力破解攻击
- 抵御DDoS攻击
- 防止API滥用
- 确保公平使用
- 降低服务器成本
Implementation with Express Rate Limit
使用Express Rate Limit实现
```javascript
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const Redis = require('ioredis');
// Create Redis client
const redis = new Redis({
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT
});
// General API rate limit
const apiLimiter = rateLimit({
store: new RedisStore({
client: redis,
prefix: 'rl:api:'
}),
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per window
message: {
error: 'Too many requests, please try again later',
retryAfter: 900 // seconds
},
standardHeaders: true, // Return rate limit info in headers
legacyHeaders: false,
// Custom key generator (by user ID or IP)
keyGenerator: (req) => {
return req.user?.userId || req.ip;
}
});
// Strict rate limit for authentication endpoints
const authLimiter = rateLimit({
store: new RedisStore({
client: redis,
prefix: 'rl:auth:'
}),
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // Only 5 login attempts per 15 minutes
skipSuccessfulRequests: true, // Don't count successful logins
message: {
error: 'Too many login attempts, please try again later',
retryAfter: 900
}
});
// Apply rate limiters
app.use('/api/', apiLimiter);
app.use('/api/auth/login', authLimiter);
app.use('/api/auth/register', authLimiter);
// Custom rate limiter for expensive operations
const expensiveLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1 hour
max: 10, // 10 requests per hour
message: {
error: 'Rate limit exceeded for this operation'
}
});
app.post('/api/reports/generate',
authenticateToken,
expensiveLimiter,
async (req, res) => {
// Expensive operation
}
);
```
javascript
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const Redis = require('ioredis');
// 创建Redis客户端
const redis = new Redis({
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT
});
// 通用API速率限制
const apiLimiter = rateLimit({
store: new RedisStore({
client: redis,
prefix: 'rl:api:'
}),
windowMs: 15 * 60 * 1000, // 15分钟
max: 100, // 每个时间窗口允许100次请求
message: {
error: 'Too many requests, please try again later',
retryAfter: 900 // 秒
},
standardHeaders: true, // 在响应头中返回速率限制信息
legacyHeaders: false,
// 自定义密钥生成器(按用户ID或IP)
keyGenerator: (req) => {
return req.user?.userId || req.ip;
}
});
// 针对身份验证端点的严格速率限制
const authLimiter = rateLimit({
store: new RedisStore({
client: redis,
prefix: 'rl:auth:'
}),
windowMs: 15 * 60 * 1000, // 15分钟
max: 5, // 每15分钟仅允许5次登录尝试
skipSuccessfulRequests: true, // 不统计成功的登录请求
message: {
error: 'Too many login attempts, please try again later',
retryAfter: 900
}
});
// 应用速率限制器
app.use('/api/', apiLimiter);
app.use('/api/auth/login', authLimiter);
app.use('/api/auth/register', authLimiter);
// 针对高开销操作的自定义速率限制器
const expensiveLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1小时
max: 10, // 每小时允许10次请求
message: {
error: 'Rate limit exceeded for this operation'
}
});
app.post('/api/reports/generate',
authenticateToken,
expensiveLimiter,
async (req, res) => {
// 高开销操作
}
);Advanced: Per-User Rate Limiting
进阶:按用户分级的速率限制
```javascript
// Different limits based on user tier
function createTieredRateLimiter() {
const limits = {
free: { windowMs: 60 * 60 * 1000, max: 100 },
pro: { windowMs: 60 * 60 * 1000, max: 1000 },
enterprise: { windowMs: 60 * 60 * 1000, max: 10000 }
};
return async (req, res, next) => {
const user = req.user;
const tier = user?.tier || 'free';
const limit = limits[tier];
const key = \`rl:user:\${user.userId}\`;
const current = await redis.incr(key);
if (current === 1) {
await redis.expire(key, limit.windowMs / 1000);
}
if (current > limit.max) {
return res.status(429).json({
error: 'Rate limit exceeded',
limit: limit.max,
remaining: 0,
reset: await redis.ttl(key)
});
}
// Set rate limit headers
res.set({
'X-RateLimit-Limit': limit.max,
'X-RateLimit-Remaining': limit.max - current,
'X-RateLimit-Reset': await redis.ttl(key)
});
next();};
}
app.use('/api/', authenticateToken, createTieredRateLimiter());
```
javascript
// 根据用户等级设置不同的限制
function createTieredRateLimiter() {
const limits = {
free: { windowMs: 60 * 60 * 1000, max: 100 },
pro: { windowMs: 60 * 60 * 1000, max: 1000 },
enterprise: { windowMs: 60 * 60 * 1000, max: 10000 }
};
return async (req, res, next) => {
const user = req.user;
const tier = user?.tier || 'free';
const limit = limits[tier];
const key = `rl:user:${user.userId}`;
const current = await redis.incr(key);
if (current === 1) {
await redis.expire(key, limit.windowMs / 1000);
}
if (current > limit.max) {
return res.status(429).json({
error: 'Rate limit exceeded',
limit: limit.max,
remaining: 0,
reset: await redis.ttl(key)
});
}
// 设置速率限制响应头
res.set({
'X-RateLimit-Limit': limit.max,
'X-RateLimit-Remaining': limit.max - current,
'X-RateLimit-Reset': await redis.ttl(key)
});
next();
};
}
app.use('/api/', authenticateToken, createTieredRateLimiter());DDoS Protection with Helmet
使用Helmet实现DDoS防护
```javascript
const helmet = require('helmet');
app.use(helmet({
// Content Security Policy
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", 'data:', 'https:']
}
},
// Prevent clickjacking
frameguard: { action: 'deny' },
// Hide X-Powered-By header
hidePoweredBy: true,
// Prevent MIME type sniffing
noSniff: true,
// Enable HSTS
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
}
}));
```
javascript
const helmet = require('helmet');
app.use(helmet({
// 内容安全策略
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", 'data:', 'https:']
}
},
// 防止点击劫持
frameguard: { action: 'deny' },
// 隐藏X-Powered-By头
hidePoweredBy: true,
// 防止MIME类型嗅探
noSniff: true,
// 启用HSTS
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
}
}));Rate Limit Response Headers
速率限制响应头
```
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1640000000
Retry-After: 900
```
undefinedX-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1640000000
Retry-After: 900undefinedBest Practices
最佳实践
✅ Do This
✅ 推荐做法
- Use HTTPS Everywhere - Never send sensitive data over HTTP
- Implement Authentication - Require authentication for protected endpoints
- Validate All Inputs - Never trust user input
- Use Parameterized Queries - Prevent SQL injection
- Implement Rate Limiting - Protect against brute force and DDoS
- Hash Passwords - Use bcrypt with salt rounds >= 10
- Use Short-Lived Tokens - JWT access tokens should expire quickly
- Implement CORS Properly - Only allow trusted origins
- Log Security Events - Monitor for suspicious activity
- Keep Dependencies Updated - Regularly update packages
- Use Security Headers - Implement Helmet.js
- Sanitize Error Messages - Don't leak sensitive information
- 全程使用HTTPS - 绝不要通过HTTP传输敏感数据
- 实现身份验证 - 受保护的端点必须要求身份验证
- 验证所有输入 - 绝不信任用户输入
- 使用参数化查询 - 防止SQL注入
- 实现速率限制 - 抵御暴力破解和DDoS攻击
- 哈希密码 - 使用bcrypt,盐值轮次 >=10
- 使用短生命周期令牌 - JWT访问令牌应快速过期
- 正确配置CORS - 仅允许可信来源
- 记录安全事件 - 监控可疑活动
- 保持依赖更新 - 定期更新依赖包
- 使用安全HTTP头 - 实现Helmet.js
- 清理错误信息 - 不要泄露敏感信息
❌ Don't Do This
❌ 禁止做法
- Don't Store Passwords in Plain Text - Always hash passwords
- Don't Use Weak Secrets - Use strong, random JWT secrets
- Don't Trust User Input - Always validate and sanitize
- Don't Expose Stack Traces - Hide error details in production
- Don't Use String Concatenation for SQL - Use parameterized queries
- Don't Store Sensitive Data in JWT - JWTs are not encrypted
- Don't Ignore Security Updates - Update dependencies regularly
- Don't Use Default Credentials - Change all default passwords
- Don't Disable CORS Completely - Configure it properly instead
- Don't Log Sensitive Data - Sanitize logs
- 不要明文存储密码 - 始终哈希密码
- 不要使用弱密钥 - 使用强随机的JWT密钥
- 不要信任用户输入 - 始终验证和清理
- 不要暴露堆栈跟踪 - 在生产环境隐藏错误详情
- 不要使用字符串拼接构建SQL语句 - 使用参数化查询
- 不要在JWT中存储敏感数据 - JWT未加密
- 不要忽略安全更新 - 定期更新依赖
- 不要使用默认凭证 - 修改所有默认密码
- 不要完全禁用CORS - 正确配置它
- 不要记录敏感数据 - 清理日志内容
Common Pitfalls
常见陷阱
Problem: JWT Secret Exposed in Code
问题:JWT密钥在代码中暴露
Symptoms: JWT secret hardcoded or committed to Git
Solution:
```javascript
// ❌ Bad
const JWT_SECRET = 'my-secret-key';
// ✅ Good
const JWT_SECRET = process.env.JWT_SECRET;
if (!JWT_SECRET) {
throw new Error('JWT_SECRET environment variable is required');
}
// Generate strong secret
// node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
```
症状: JWT密钥硬编码或提交到Git
解决方案:
javascript
// ❌ 错误做法
const JWT_SECRET = 'my-secret-key';
// ✅ 正确做法
const JWT_SECRET = process.env.JWT_SECRET;
if (!JWT_SECRET) {
throw new Error('JWT_SECRET environment variable is required');
}
// 生成强密钥
// node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"Problem: Weak Password Requirements
问题:弱密码要求
Symptoms: Users can set weak passwords like "password123"
Solution:
```javascript
const passwordSchema = z.string()
.min(12, 'Password must be at least 12 characters')
.regex(/[A-Z]/, 'Must contain uppercase letter')
.regex(/[a-z]/, 'Must contain lowercase letter')
.regex(/[0-9]/, 'Must contain number')
.regex(/[^A-Za-z0-9]/, 'Must contain special character');
// Or use a password strength library
const zxcvbn = require('zxcvbn');
const result = zxcvbn(password);
if (result.score < 3) {
return res.status(400).json({
error: 'Password too weak',
suggestions: result.feedback.suggestions
});
}
```
症状: 用户可以设置类似"password123"的弱密码
解决方案:
javascript
const passwordSchema = z.string()
.min(12, 'Password must be at least 12 characters')
.regex(/[A-Z]/, 'Must contain uppercase letter')
.regex(/[a-z]/, 'Must contain lowercase letter')
.regex(/[0-9]/, 'Must contain number')
.regex(/[^A-Za-z0-9]/, 'Must contain special character');
// 或者使用密码强度库
const zxcvbn = require('zxcvbn');
const result = zxcvbn(password);
if (result.score < 3) {
return res.status(400).json({
error: 'Password too weak',
suggestions: result.feedback.suggestions
});
}Problem: Missing Authorization Checks
问题:缺少授权检查
Symptoms: Users can access resources they shouldn't
Solution:
```javascript
// ❌ Bad: Only checks authentication
app.delete('/api/posts/:id', authenticateToken, async (req, res) => {
await prisma.post.delete({ where: { id: req.params.id } });
res.json({ success: true });
});
// ✅ Good: Checks both authentication and authorization
app.delete('/api/posts/:id', authenticateToken, async (req, res) => {
const post = await prisma.post.findUnique({
where: { id: req.params.id }
});
if (!post) {
return res.status(404).json({ error: 'Post not found' });
}
// Check if user owns the post or is admin
if (post.userId !== req.user.userId && req.user.role !== 'admin') {
return res.status(403).json({
error: 'Not authorized to delete this post'
});
}
await prisma.post.delete({ where: { id: req.params.id } });
res.json({ success: true });
});
```
症状: 用户可以访问不属于自己的资源
解决方案:
javascript
// ❌ 错误做法:仅检查身份验证
app.delete('/api/posts/:id', authenticateToken, async (req, res) => {
await prisma.post.delete({ where: { id: req.params.id } });
res.json({ success: true });
});
// ✅ 正确做法:同时检查身份验证和授权
app.delete('/api/posts/:id', authenticateToken, async (req, res) => {
const post = await prisma.post.findUnique({
where: { id: req.params.id }
});
if (!post) {
return res.status(404).json({ error: 'Post not found' });
}
// 检查用户是否拥有该帖子或是否为管理员
if (post.userId !== req.user.userId && req.user.role !== 'admin') {
return res.status(403).json({
error: 'Not authorized to delete this post'
});
}
await prisma.post.delete({ where: { id: req.params.id } });
res.json({ success: true });
});Problem: Verbose Error Messages
问题:详细的错误信息
Symptoms: Error messages reveal system details
Solution:
```javascript
// ❌ Bad: Exposes database details
app.post('/api/users', async (req, res) => {
try {
const user = await prisma.user.create({ data: req.body });
res.json(user);
} catch (error) {
res.status(500).json({ error: error.message });
// Error: "Unique constraint failed on the fields: ()"
}
});
email// ✅ Good: Generic error message
app.post('/api/users', async (req, res) => {
try {
const user = await prisma.user.create({ data: req.body });
res.json(user);
} catch (error) {
console.error('User creation error:', error); // Log full error
if (error.code === 'P2002') {
return res.status(400).json({
error: 'Email already exists'
});
}
res.status(500).json({
error: 'An error occurred while creating user'
});}
});
```
症状: 错误信息泄露系统细节
解决方案:
javascript
// ❌ 错误做法:暴露数据库细节
app.post('/api/users', async (req, res) => {
try {
const user = await prisma.user.create({ data: req.body });
res.json(user);
} catch (error) {
res.status(500).json({ error: error.message });
// 错误信息:"Unique constraint failed on the fields: (`email`)"
}
});
// ✅ 正确做法:通用错误信息
app.post('/api/users', async (req, res) => {
try {
const user = await prisma.user.create({ data: req.body });
res.json(user);
} catch (error) {
console.error('User creation error:', error); // 记录完整错误
if (error.code === 'P2002') {
return res.status(400).json({
error: 'Email already exists'
});
}
res.status(500).json({
error: 'An error occurred while creating user'
});
}
});Security Checklist
安全检查清单
Authentication & Authorization
身份验证与授权
- Implement strong authentication (JWT, OAuth 2.0)
- Use HTTPS for all endpoints
- Hash passwords with bcrypt (salt rounds >= 10)
- Implement token expiration
- Add refresh token mechanism
- Verify user authorization for each request
- Implement role-based access control (RBAC)
- 实现强身份验证(JWT、OAuth 2.0)
- 所有端点使用HTTPS
- 使用bcrypt哈希密码(盐值轮次 >=10)
- 实现令牌过期机制
- 添加刷新令牌机制
- 为每个请求验证用户授权
- 实现基于角色的访问控制(RBAC)
Input Validation
输入验证
- Validate all user inputs
- Use parameterized queries or ORM
- Sanitize HTML content
- Validate file uploads
- Implement request schema validation
- Use allowlists, not blocklists
- 验证所有用户输入
- 使用参数化查询或ORM
- 清理HTML内容
- 验证文件上传
- 实现请求schema验证
- 使用白名单,而非黑名单
Rate Limiting & DDoS Protection
速率限制与DDoS防护
- Implement rate limiting per user/IP
- Add stricter limits for auth endpoints
- Use Redis for distributed rate limiting
- Return proper rate limit headers
- Implement request throttling
- 按用户/IP实现速率限制
- 为身份验证端点设置更严格的限制
- 使用Redis实现分布式速率限制
- 返回正确的速率限制响应头
- 实现请求流量控制
Data Protection
数据保护
- Use HTTPS/TLS for all traffic
- Encrypt sensitive data at rest
- Don't store sensitive data in JWT
- Sanitize error messages
- Implement proper CORS configuration
- Use security headers (Helmet.js)
- 所有流量使用HTTPS/TLS
- 加密静态存储的敏感数据
- 不要在JWT中存储敏感数据
- 清理错误信息
- 正确配置CORS
- 使用安全HTTP头(Helmet.js)
Monitoring & Logging
监控与日志
- Log security events
- Monitor for suspicious activity
- Set up alerts for failed auth attempts
- Track API usage patterns
- Don't log sensitive data
- 记录安全事件
- 监控可疑活动
- 为失败的身份验证尝试设置告警
- 跟踪API使用模式
- 不要记录敏感数据
OWASP API Security Top 10
OWASP API安全Top 10
- Broken Object Level Authorization - Always verify user can access resource
- Broken Authentication - Implement strong authentication mechanisms
- Broken Object Property Level Authorization - Validate which properties user can access
- Unrestricted Resource Consumption - Implement rate limiting and quotas
- Broken Function Level Authorization - Verify user role for each function
- Unrestricted Access to Sensitive Business Flows - Protect critical workflows
- Server Side Request Forgery (SSRF) - Validate and sanitize URLs
- Security Misconfiguration - Use security best practices and headers
- Improper Inventory Management - Document and secure all API endpoints
- Unsafe Consumption of APIs - Validate data from third-party APIs
- 对象级授权失效 - 始终验证用户是否有权访问资源
- 身份验证机制失效 - 实现强身份验证机制
- 对象属性级授权失效 - 验证用户可访问的属性
- 资源消耗无限制 - 实现速率限制和配额
- 功能级授权失效 - 为每个功能验证用户角色
- 敏感业务流访问无限制 - 保护关键工作流
- 服务器端请求伪造(SSRF) - 验证和清理URL
- 安全配置错误 - 使用安全最佳实践和HTTP头
- 资产管理不当 - 记录并保护所有API端点
- 不安全的API消费 - 验证来自第三方API的数据
Related Skills
相关技能
- - Security testing perspective
@ethical-hacking-methodology - - Testing for SQL injection
@sql-injection-testing - - Testing for XSS vulnerabilities
@xss-html-injection - - Authentication vulnerabilities
@broken-authentication - - Backend development standards
@backend-dev-guidelines - - Debug security issues
@systematic-debugging
- - 安全测试视角
@ethical-hacking-methodology - - SQL注入测试
@sql-injection-testing - - XSS漏洞测试
@xss-html-injection - - 身份验证漏洞
@broken-authentication - - 后端开发标准
@backend-dev-guidelines - - 调试安全问题
@systematic-debugging
Additional Resources
额外资源
- OWASP API Security Top 10
- JWT Best Practices
- Express Security Best Practices
- Node.js Security Checklist
- API Security Checklist
Pro Tip: Security is not a one-time task - regularly audit your APIs, keep dependencies updated, and stay informed about new vulnerabilities!
- OWASP API Security Top 10
- JWT Best Practices
- Express Security Best Practices
- Node.js Security Checklist
- API Security Checklist
专业提示: 安全不是一次性任务 - 定期审计你的API,保持依赖更新,并及时了解新的漏洞!