brandonwise-api-security

TotalClaw 作者 totalclaw

实现安全的 API 设计模式,包括认证、授权、输入验证、速率限制,以及针对常见 API 漏洞的防护。

安装 / 下载方式

TotalClaw CLI推荐
totalclaw install totalclaw:totalclaw~brandonwise-api-security
cURL直接下载,无需登录
curl -fsSL https://skills.taituai.com/api/skills/totalclaw%3Atotalclaw~brandonwise-api-security/file -o brandonwise-api-security.md
## 概述(中文)

实现安全的 API 设计模式,包括认证、授权、输入验证、速率限制,以及针对常见 API 漏洞的防护。

## 技能正文

# API 安全最佳实践

实现安全的 API 设计模式,包括认证、授权、输入验证、速率限制,以及针对常见 API 漏洞的防护。

## 描述

适用场景:
- 设计新 API 端点
- 保护现有 API
- 实现认证与授权(JWT、OAuth 2.0、API 密钥)
- 设置速率限制与节流
- 防护注入攻击(SQL、XSS、命令)
- 进行 API 安全审查或审计准备
- 在 API 中处理敏感数据
- 构建 REST、GraphQL 或 WebSocket API

不适用场景:
- 需要漏洞扫描(使用 `vulnerability-scanner` 技能)
- 仅前端应用无 API
- 需要网络层安全(防火墙、WAF 配置)

输出:
- 安全认证实现(JWT、刷新令牌)
- 输入验证模式(Zod、Joi)
- 速率限制配置
- 安全中间件示例
- OWASP API Top 10 合规指南

---

## 工作原理

### 步骤 1:认证与授权

- 选择认证方式(JWT、OAuth 2.0、API 密钥)
- 实现基于令牌的认证
- 设置基于角色的访问控制(RBAC)
- 安全会话管理
- 实现多因素认证(MFA)

### 步骤 2:输入验证与净化

- 验证所有输入数据
- 净化用户输入
- 使用参数化查询
- 实现请求模式验证
- 防止 SQL 注入、XSS 和命令注入

### 步骤 3:速率限制与节流

- 按用户/IP 实现速率限制
- 设置 API 节流
- 配置请求配额
- 优雅处理速率限制错误
- 监控可疑活动

### 步骤 4:数据保护

- 传输中加密(HTTPS/TLS)
- 静态敏感数据加密
- 正确错误处理(无数据泄露)
- 净化错误消息
- 使用安全头

---

## JWT 认证实现

### 生成安全 JWT 令牌

```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 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' });
  }
});
```

### JWT 验证中间件

```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 };
```

---

## 输入验证(SQL 注入防护)

### ❌ 脆弱代码

```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 → 返回所有用户!
```

### ✅ 安全:参数化查询

```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);
});
```

### ✅ 安全:使用 ORM (Prisma)

```javascript
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);
});
```

### 使用 Zod 的模式验证

```javascript
const { z } = require('zod');

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]/, 'Must contain uppercase letter')
    .regex(/[a-z]/, 'Must contain lowercase letter')
    .regex(/[0-9]/, 'Must contain number'),
  name: z.string().min(2).max(100),
  age: z.number().int().min(18).max(120).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);
});
```

---

## 速率限制

```javascript
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const Redis = require('ioredis');

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,
  keyGenerator: (req) => req.user?.userId || req.ip
});

// 认证端点严格限速
const authLimiter = rateLimit({
  store: new RedisStore({ client: redis, prefix: 'rl:auth:' }),
  windowMs: 15 * 60 * 1000,
  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);
```

---

## 安全头(Helmet)

```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' },
  hidePoweredBy: true,
  noSniff: true,
  hsts: { maxAge: 31536000, includeSubDomains: true, preload: true }
}));
```

---

## 授权检查

### ❌ 差:仅认证

```javascript
app.delete('/api/posts/:id', authenticateToken, async (req, res) => {
  await prisma.post.delete({ where: { id: req.params.id } });
  res.json({ success: true });
});
```

### ✅ 好:认证 + 授权

```javascript
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 });
});
```

---

## 最佳实践

### ✅ 应该做

- **全程使用 HTTPS** — 绝不在 HTTP 上传输敏感数据
- **实现认证** — 受保护端点需要认证
- **验证所有输入** — 绝不信任用户输入
- **使用参数化查询** — 防止 SQL 注入
- **实现速率限制** — 防护暴力破解和 DDoS
- **哈希密码** — 使用 bcrypt,盐轮 >= 10
- **使用短生命周期令牌