API安全最佳实践 2026
API安全最佳实践 2026
REST API和GraphQL API的安全防护完全指南。认证、授权、速率限制等。
2026年3月16日8分钟阅读
前言:为什么API安全至关重要
在2026年,API已经成为现代软件架构的核心组成部分。从移动应用到微服务通信,从第三方集成到物联网设备,API无处不在。然而,API也是攻击者最常瞄准的目标之一。OWASP API Security Top 10每年都在更新,新的攻击手法不断出现。
一个安全漏洞可能导致数据泄露、服务中断、经济损失甚至法律诉讼。本指南将全面覆盖REST API和GraphQL API的安全防护策略,帮助你构建坚如磐石的API安全体系。
一、认证(Authentication)
认证回答的是"你是谁"的问题。选择正确的认证方案是API安全的第一步。
1.1 JWT(JSON Web Token)最佳实践
JWT是目前最流行的API认证方案之一,但错误的使用方式会带来严重的安全风险:
// Node.js中的JWT实现
import jwt from 'jsonwebtoken';
// 生成Token
function generateTokens(user) {
// Access Token - 短期有效
const accessToken = jwt.sign(
{
sub: user.id,
role: user.role,
type: 'access',
},
process.env.JWT_ACCESS_SECRET,
{
expiresIn: '15m', // 15分钟过期
issuer: 'your-app.com',
audience: 'your-app-api',
algorithm: 'RS256', // 使用非对称算法
}
);
// Refresh Token - 长期有效
const refreshToken = jwt.sign(
{
sub: user.id,
type: 'refresh',
jti: generateUniqueId(), // 唯一标识符,用于黑名单
},
process.env.JWT_REFRESH_SECRET,
{
expiresIn: '7d',
issuer: 'your-app.com',
}
);
return { accessToken, refreshToken };
}
// 验证Token的中间件
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ error: '未提供认证令牌' });
}
try {
const payload = jwt.verify(token, process.env.JWT_ACCESS_SECRET, {
algorithms: ['RS256'], // 严格限制算法
issuer: 'your-app.com',
audience: 'your-app-api',
});
req.user = payload;
next();
} catch (error) {
if (error instanceof jwt.TokenExpiredError) {
return res.status(401).json({ error: '令牌已过期' });
}
return res.status(403).json({ error: '无效的令牌' });
}
}
JWT安全要点:
- 使用
RS256(非对称)而非HS256(对称),防止密钥泄露 - Access Token有效期控制在15分钟以内
- 永远不要在JWT中存储敏感信息(密码、信用卡号等)
- 实现Token黑名单机制以支持强制登出
- 验证时严格指定允许的算法,防止算法切换攻击
1.2 OAuth 2.0与PKCE
对于需要第三方授权的场景,OAuth 2.0 + PKCE是推荐方案:
// PKCE流程 - 适用于SPA和移动应用
import crypto from 'crypto';
// 1. 生成code verifier和code challenge
function generatePKCE() {
const codeVerifier = crypto.randomBytes(32)
.toString('base64url');
const codeChallenge = crypto
.createHash('sha256')
.update(codeVerifier)
.digest('base64url');
return { codeVerifier, codeChallenge };
}
// 2. 构建授权URL
function buildAuthorizationUrl(codeChallenge) {
const params = new URLSearchParams({
response_type: 'code',
client_id: process.env.CLIENT_ID,
redirect_uri: 'https://your-app.com/callback',
scope: 'openid profile email',
state: crypto.randomBytes(16).toString('hex'),
code_challenge: codeChallenge,
code_challenge_method: 'S256',
});
return `https://auth-server.com/authorize?${params}`;
}
// 3. 用授权码+code verifier换取Token
async function exchangeCode(code, codeVerifier) {
const response = await fetch('https://auth-server.com/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code,
redirect_uri: 'https://your-app.com/callback',
client_id: process.env.CLIENT_ID,
code_verifier: codeVerifier,
}),
});
return response.json();
}
1.3 API Key管理
对于服务器之间的通信,API Key是一种简单有效的认证方式:
// API Key验证中间件
function validateApiKey(req, res, next) {
const apiKey = req.headers['x-api-key'];
if (!apiKey) {
return res.status(401).json({ error: '缺少API Key' });
}
// 使用时序安全的比较防止时序攻击
const validKey = process.env.API_KEY;
if (!crypto.timingSafeEqual(
Buffer.from(apiKey),
Buffer.from(validKey)
)) {
return res.status(403).json({ error: '无效的API Key' });
}
next();
}
API Key安全要点:
- 永远不要在客户端代码中暴露API Key
- 为每个客户端/用途分配独立的Key
- 实现Key轮换机制
- 记录所有API Key的使用日志
二、授权(Authorization)
授权回答的是"你能做什么"的问题。
2.1 基于角色的访问控制(RBAC)
// 定义角色和权限
enum Permission {
READ_USERS = 'read:users',
WRITE_USERS = 'write:users',
DELETE_USERS = 'delete:users',
READ_POSTS = 'read:posts',
WRITE_POSTS = 'write:posts',
DELETE_POSTS = 'delete:posts',
MANAGE_SETTINGS = 'manage:settings',
}
const rolePermissions: Record<string, Permission[]> = {
admin: [
Permission.READ_USERS,
Permission.WRITE_USERS,
Permission.DELETE_USERS,
Permission.READ_POSTS,
Permission.WRITE_POSTS,
Permission.DELETE_POSTS,
Permission.MANAGE_SETTINGS,
],
editor: [
Permission.READ_USERS,
Permission.READ_POSTS,
Permission.WRITE_POSTS,
Permission.DELETE_POSTS,
],
viewer: [
Permission.READ_USERS,
Permission.READ_POSTS,
],
};
// 授权中间件
function requirePermission(...permissions: Permission[]) {
return (req: Request, res: Response, next: NextFunction) => {
const userRole = req.user?.role;
if (!userRole) {
return res.status(401).json({ error: '未认证' });
}
const userPermissions = rolePermissions[userRole] || [];
const hasPermission = permissions.every(p =>
userPermissions.includes(p)
);
if (!hasPermission) {
return res.status(403).json({ error: '权限不足' });
}
next();
};
}
// 使用
app.delete(
'/api/users/:id',
authenticateToken,
requirePermission(Permission.DELETE_USERS),
deleteUserHandler
);
2.2 基于属性的访问控制(ABAC)
对于更复杂的授权需求,ABAC提供更细粒度的控制:
// ABAC策略引擎
interface AccessContext {
user: { id: string; role: string; department: string };
resource: { type: string; ownerId: string; status: string };
action: string;
environment: { time: Date; ipAddress: string };
}
function evaluatePolicy(context: AccessContext): boolean {
const { user, resource, action } = context;
// 规则1:管理员可以执行任何操作
if (user.role === 'admin') return true;
// 规则2:用户只能修改自己的资源
if (action === 'update' && resource.ownerId !== user.id) {
return false;
}
// 规则3:已发布的资源只有编辑和管理员可以修改
if (resource.status === 'published' && action === 'update') {
return ['admin', 'editor'].includes(user.role);
}
// 规则4:删除操作需要同部门
if (action === 'delete') {
return user.department === resource.department;
}
return true;
}
三、输入验证与数据清理
输入验证是防御注入攻击的第一道防线。
3.1 请求验证
import { z } from 'zod';
// 使用Zod定义严格的输入Schema
const createUserSchema = z.object({
name: z.string()
.min(2, '名称至少2个字符')
.max(50, '名称不超过50个字符')
.trim(),
email: z.string()
.email('无效的邮箱格式')
.toLowerCase(),
password: z.string()
.min(12, '密码至少12个字符')
.regex(/[A-Z]/, '需要包含大写字母')
.regex(/[a-z]/, '需要包含小写字母')
.regex(/[0-9]/, '需要包含数字')
.regex(/[^A-Za-z0-9]/, '需要包含特殊字符'),
age: z.number()
.int()
.min(13, '年龄不能小于13')
.max(150, '年龄不能大于150'),
role: z.enum(['viewer', 'editor']), // 不允许通过API创建admin
});
// 验证中间件
function validateBody(schema: z.ZodSchema) {
return (req: Request, res: Response, next: NextFunction) => {
const result = schema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({
error: '输入验证失败',
details: result.error.issues.map(issue => ({
field: issue.path.join('.'),
message: issue.message,
})),
});
}
req.body = result.data; // 使用验证后的数据
next();
};
}
app.post('/api/users', validateBody(createUserSchema), createUserHandler);
3.2 防止SQL注入
// 差:字符串拼接(极度危险!)
const query = `SELECT * FROM users WHERE email = '${email}'`;
// 好:使用参数化查询
const result = await db.query(
'SELECT * FROM users WHERE email = $1',
[email]
);
// 更好:使用ORM
const user = await prisma.user.findUnique({
where: { email },
});
3.3 防止NoSQL注入
// MongoDB中的NoSQL注入防护
// 差:直接使用用户输入
const user = await User.findOne({ username: req.body.username });
// 攻击者可以发送: { "username": { "$gt": "" } }
// 好:确保输入是字符串类型
const username = String(req.body.username);
const user = await User.findOne({ username });
// 使用express-mongo-sanitize中间件
import mongoSanitize from 'express-mongo-sanitize';
app.use(mongoSanitize());
四、速率限制(Rate Limiting)
4.1 多层速率限制
import rateLimit from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
// 全局速率限制
const globalLimiter = rateLimit({
store: new RedisStore({ sendCommand: (...args) => redis.call(...args) }),
windowMs: 15 * 60 * 1000, // 15分钟窗口
max: 1000, // 每窗口1000次请求
standardHeaders: true,
legacyHeaders: false,
message: { error: '请求过于频繁,请稍后再试' },
});
// 登录接口专用限制(更严格)
const loginLimiter = rateLimit({
store: new RedisStore({ sendCommand: (...args) => redis.call(...args) }),
windowMs: 15 * 60 * 1000,
max: 5, // 15分钟内最多5次尝试
skipSuccessfulRequests: true, // 成功的请求不计入限制
message: { error: '登录尝试次数过多,请15分钟后再试' },
});
// API密钥级别的限制
const apiKeyLimiter = rateLimit({
store: new RedisStore({ sendCommand: (...args) => redis.call(...args) }),
windowMs: 60 * 1000, // 1分钟窗口
max: 100, // 每分钟100次
keyGenerator: (req) => req.headers['x-api-key'] || req.ip,
});
app.use('/api/', globalLimiter);
app.use('/api/auth/login', loginLimiter);
app.use('/api/v1/', apiKeyLimiter);
4.2 滑动窗口算法
对于更精确的速率控制,使用滑动窗口算法:
async function slidingWindowRateLimit(
key: string,
maxRequests: number,
windowMs: number
): Promise<{ allowed: boolean; remaining: number; resetAt: number }> {
const now = Date.now();
const windowStart = now - windowMs;
const pipeline = redis.pipeline();
pipeline.zremrangebyscore(key, 0, windowStart); // 清理过期记录
pipeline.zadd(key, now, `${now}-${Math.random()}`); // 添加当前请求
pipeline.zcard(key); // 获取窗口内请求数
pipeline.pexpire(key, windowMs); // 设置过期时间
const results = await pipeline.exec();
const requestCount = results[2][1] as number;
return {
allowed: requestCount <= maxRequests,
remaining: Math.max(0, maxRequests - requestCount),
resetAt: now + windowMs,
};
}
五、安全响应头
import helmet from 'helmet';
// 使用Helmet设置安全响应头
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'data:', 'https:'],
connectSrc: ["'self'", 'https://api.your-app.com'],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true,
},
}));
// 手动设置额外的安全头
app.use((req, res, next) => {
// 防止MIME类型嗅探
res.setHeader('X-Content-Type-Options', 'nosniff');
// 防止点击劫持
res.setHeader('X-Frame-Options', 'DENY');
// 启用XSS保护
res.setHeader('X-XSS-Protection', '1; mode=block');
// 控制Referrer信息
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
// 限制浏览器功能
res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
next();
});
六、GraphQL特定安全措施
GraphQL有其独特的安全挑战:
6.1 查询复杂度限制
import depthLimit from 'graphql-depth-limit';
import { createComplexityLimitRule } from 'graphql-validation-complexity';
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [
depthLimit(5), // 最大查询深度
createComplexityLimitRule(1000, { // 最大查询复杂度
scalarCost: 1,
objectCost: 10,
listFactor: 20,
}),
],
});
6.2 禁用内省(生产环境)
const server = new ApolloServer({
typeDefs,
resolvers,
introspection: process.env.NODE_ENV !== 'production',
});
6.3 查询白名单(Persisted Queries)
// 只允许预定义的查询
const allowedQueries = {
'abc123': 'query GetUser($id: ID!) { user(id: $id) { name email } }',
'def456': 'query GetPosts { posts { title content } }',
};
app.use('/graphql', (req, res, next) => {
if (process.env.NODE_ENV === 'production') {
const queryId = req.body.extensions?.persistedQuery?.sha256Hash;
if (!queryId || !allowedQueries[queryId]) {
return res.status(400).json({ error: '不允许的查询' });
}
req.body.query = allowedQueries[queryId];
}
next();
});
七、日志与监控
7.1 安全日志记录
import winston from 'winston';
const securityLogger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json(),
),
transports: [
new winston.transports.File({ filename: 'security.log' }),
],
});
// 记录关键安全事件
function logSecurityEvent(event: {
type: string;
userId?: string;
ip: string;
details: Record<string, unknown>;
}) {
securityLogger.warn('Security Event', {
...event,
timestamp: new Date().toISOString(),
});
}
// 在认证失败时记录
app.post('/api/auth/login', async (req, res) => {
const result = await authenticate(req.body);
if (!result.success) {
logSecurityEvent({
type: 'AUTH_FAILURE',
ip: req.ip,
details: {
email: req.body.email,
reason: result.reason,
userAgent: req.headers['user-agent'],
},
});
}
});
7.2 异常检测
监控以下指标来检测潜在的攻击:
- 单个IP的异常高请求量
- 大量的401/403错误
- 异常的请求模式(如顺序扫描API端点)
- 请求体中的可疑内容(SQL关键字、XSS载荷)
- 非常规时间段的API访问
八、CORS配置
import cors from 'cors';
// 严格的CORS配置
const corsOptions = {
origin: function (origin, callback) {
const allowedOrigins = [
'https://your-app.com',
'https://admin.your-app.com',
];
// 允许没有origin的请求(如移动应用和Postman)
if (!origin) return callback(null, true);
if (allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('CORS策略不允许此来源'));
}
},
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-API-Key'],
credentials: true,
maxAge: 86400, // 预检请求缓存24小时
};
app.use(cors(corsOptions));
九、API安全检查清单
基础安全
- 所有API端点都通过HTTPS提供服务
- 实现了适当的认证机制(JWT/OAuth 2.0)
- 实现了基于角色的访问控制
- 所有用户输入都经过验证和清理
- 使用参数化查询防止SQL注入
- 配置了CORS策略
中级安全
- 实现了多层速率限制
- 设置了安全响应头(使用Helmet)
- JWT使用非对称算法(RS256)
- 实现了Token刷新和黑名单机制
- API Key使用时序安全的比较
- 记录了完整的安全日志
高级安全
- 实现了异常检测和告警
- GraphQL查询有深度和复杂度限制
- 生产环境禁用了GraphQL内省
- 实现了请求签名验证
- 定期进行安全审计和渗透测试
- 制定了安全事件响应计划
相关工具
在API开发和安全测试中,以下工具可以提供帮助:
- 使用 JSON格式化工具 来检查API请求和响应的数据格式
- 使用 Base64编解码工具 来分析和调试JWT Token
- 使用 URL编码工具 来处理API请求中的URL参数编码
- 使用 UUID生成器 来生成唯一的API Key和请求ID
结语
API安全不是一次性的工作,而是需要持续关注和改进的过程。攻击手法在不断进化,你的防御也需要与时俱进。从本指南中的基础检查清单开始,逐步建立起完整的API安全体系。
记住安全的核心原则:永不信任用户输入、最小权限原则、纵深防御、假设已被攻破。遵循这些原则,结合本文介绍的具体技术措施,你就能构建出安全可靠的API服务。