security-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Security Patterns

安全模式

When to Load

加载时机

  • Trigger: Auth flows, encryption, secrets management, CORS configuration, input validation, rate limiting
  • Skip: No security surface involved in the current task
  • 触发场景:认证流程设计、加密、密钥管理、CORS配置、输入验证、速率限制
  • 跳过场景:当前任务不涉及任何安全层面需求

Security Implementation Workflow

安全实现工作流

Copy this checklist and track progress:
Security Implementation Progress:
- [ ] Step 1: Choose authentication strategy
- [ ] Step 2: Implement authorization model
- [ ] Step 3: Set up password hashing
- [ ] Step 4: Configure secrets management
- [ ] Step 5: Enable encryption (transit + rest)
- [ ] Step 6: Configure CORS
- [ ] Step 7: Add rate limiting
- [ ] Step 8: Validate against anti-patterns checklist
复制以下检查清单并跟踪进度:
Security Implementation Progress:
- [ ] Step 1: Choose authentication strategy
- [ ] Step 2: Implement authorization model
- [ ] Step 3: Set up password hashing
- [ ] Step 4: Configure secrets management
- [ ] Step 5: Enable encryption (transit + rest)
- [ ] Step 6: Configure CORS
- [ ] Step 7: Add rate limiting
- [ ] Step 8: Validate against anti-patterns checklist

Authentication Patterns

身份验证模式

JWT (JSON Web Tokens)

JWT(JSON Web Tokens)

typescript
import jwt from "jsonwebtoken";

function generateTokens(user: User) {
  const accessToken = jwt.sign(
    { sub: user.id, role: user.role },
    process.env.JWT_SECRET!,
    { expiresIn: "15m", algorithm: "HS256" },
  );
  const refreshToken = jwt.sign(
    { sub: user.id, tokenVersion: user.tokenVersion },
    process.env.JWT_REFRESH_SECRET!,
    { expiresIn: "7d" },
  );
  return { accessToken, refreshToken };
}

// WRONG: localStorage (XSS vulnerable) | CORRECT: httpOnly cookie for refresh, memory for access
res.cookie("refreshToken", refreshToken, {
  httpOnly: true,
  secure: true,
  sameSite: "strict",
  maxAge: 7 * 24 * 60 * 60 * 1000,
  path: "/api/auth/refresh",
});
typescript
import jwt from "jsonwebtoken";

function generateTokens(user: User) {
  const accessToken = jwt.sign(
    { sub: user.id, role: user.role },
    process.env.JWT_SECRET!,
    { expiresIn: "15m", algorithm: "HS256" },
  );
  const refreshToken = jwt.sign(
    { sub: user.id, tokenVersion: user.tokenVersion },
    process.env.JWT_REFRESH_SECRET!,
    { expiresIn: "7d" },
  );
  return { accessToken, refreshToken };
}

// 错误用法:使用localStorage(易受XSS攻击) | 正确用法:refresh令牌存入httpOnly安全Cookie,access令牌存入内存
res.cookie("refreshToken", refreshToken, {
  httpOnly: true,
  secure: true,
  sameSite: "strict",
  maxAge: 7 * 24 * 60 * 60 * 1000,
  path: "/api/auth/refresh",
});

JWT Verification Middleware

JWT验证中间件

typescript
function authenticate(req: Request, res: Response, next: NextFunction) {
  const header = req.headers.authorization;
  if (!header?.startsWith("Bearer ")) {
    return res.status(401).json({ error: "Missing token" });
  }

  try {
    const token = header.slice(7);
    const payload = jwt.verify(token, process.env.JWT_SECRET!) as JwtPayload;
    req.user = { id: payload.sub, role: payload.role };
    next();
  } catch (err) {
    if (err instanceof jwt.TokenExpiredError) {
      return res.status(401).json({ error: "Token expired" });
    }
    return res.status(401).json({ error: "Invalid token" });
  }
}
typescript
function authenticate(req: Request, res: Response, next: NextFunction) {
  const header = req.headers.authorization;
  if (!header?.startsWith("Bearer ")) {
    return res.status(401).json({ error: "Missing token" });
  }

  try {
    const token = header.slice(7);
    const payload = jwt.verify(token, process.env.JWT_SECRET!) as JwtPayload;
    req.user = { id: payload.sub, role: payload.role };
    next();
  } catch (err) {
    if (err instanceof jwt.TokenExpiredError) {
      return res.status(401).json({ error: "Token expired" });
    }
    return res.status(401).json({ error: "Invalid token" });
  }
}

Session-Based Auth

基于会话的身份验证

typescript
import session from "express-session";
import RedisStore from "connect-redis";

app.use(
  session({
    store: new RedisStore({ client: redisClient }),
    secret: process.env.SESSION_SECRET!,
    resave: false,
    saveUninitialized: false,
    cookie: {
      httpOnly: true,
      secure: process.env.NODE_ENV === "production",
      sameSite: "strict",
      maxAge: 24 * 60 * 60 * 1000, // 24 hours
    },
  }),
);
typescript
import session from "express-session";
import RedisStore from "connect-redis";

app.use(
  session({
    store: new RedisStore({ client: redisClient }),
    secret: process.env.SESSION_SECRET!,
    resave: false,
    saveUninitialized: false,
    cookie: {
      httpOnly: true,
      secure: process.env.NODE_ENV === "production",
      sameSite: "strict",
      maxAge: 24 * 60 * 60 * 1000, // 24 hours
    },
  }),
);

OAuth 2.0 / OIDC Flow Summary

OAuth 2.0 / OIDC 流程概述

Authorization Code Flow (web apps with backend):
1. Redirect to provider: /authorize?response_type=code&client_id=...&redirect_uri=...&scope=openid email
2. User authenticates, provider redirects back with ?code=AUTHORIZATION_CODE
3. Backend exchanges code for tokens (POST /token with client_secret)
4. Backend receives access_token + id_token, creates session/JWT

PKCE Flow (SPAs, mobile): Same but with code_verifier/code_challenge instead of client_secret
NEVER use Implicit Flow (deprecated, tokens exposed in URL)
授权码流程(带后端的Web应用):
1. 重定向到授权提供商:/authorize?response_type=code&client_id=...&redirect_uri=...&scope=openid email
2. 用户完成认证,提供商重定向回应用并携带?code=AUTHORIZATION_CODE
3. 后端使用code交换令牌(携带client_secret发起POST /token请求)
4. 后端获取access_token + id_token,创建会话/JWT

PKCE流程(单页应用、移动应用):流程类似,但使用code_verifier/code_challenge替代client_secret
绝对不要使用隐式流程(已废弃,令牌会暴露在URL中)

API Key Authentication

API密钥身份验证

typescript
async function authenticateApiKey(
  req: Request,
  res: Response,
  next: NextFunction,
) {
  const apiKey = req.headers["x-api-key"] as string;
  if (!apiKey) return res.status(401).json({ error: "API key required" });

  // WRONG: Direct comparison (timing attack) | CORRECT: Hash-based lookup
  const hashedKey = crypto.createHash("sha256").update(apiKey).digest("hex");
  const keyRecord = await db.apiKey.findUnique({ where: { hash: hashedKey } });
  if (!keyRecord || keyRecord.revokedAt)
    return res.status(401).json({ error: "Invalid API key" });

  req.apiClient = { id: keyRecord.clientId, scopes: keyRecord.scopes };
  next();
}
typescript
async function authenticateApiKey(
  req: Request,
  res: Response,
  next: NextFunction,
) {
  const apiKey = req.headers["x-api-key"] as string;
  if (!apiKey) return res.status(401).json({ error: "API key required" });

  // 错误用法:直接比较(易受时序攻击) | 正确用法:基于哈希的查找
  const hashedKey = crypto.createHash("sha256").update(apiKey).digest("hex");
  const keyRecord = await db.apiKey.findUnique({ where: { hash: hashedKey } });
  if (!keyRecord || keyRecord.revokedAt)
    return res.status(401).json({ error: "Invalid API key" });

  req.apiClient = { id: keyRecord.clientId, scopes: keyRecord.scopes };
  next();
}

Authorization Models

授权模型

RBAC (Role-Based Access Control)

RBAC(基于角色的访问控制)

typescript
const PERMISSIONS = {
  admin: [
    "users:read",
    "users:write",
    "users:delete",
    "posts:read",
    "posts:write",
    "posts:delete",
  ],
  editor: ["posts:read", "posts:write", "posts:delete", "users:read"],
  viewer: ["posts:read", "users:read"],
} as const;

type Role = keyof typeof PERMISSIONS;

function authorize(...requiredPermissions: string[]) {
  return (req: Request, res: Response, next: NextFunction) => {
    const userPermissions = PERMISSIONS[req.user.role as Role] || [];
    const hasPermission = requiredPermissions.every((p) =>
      (userPermissions as readonly string[]).includes(p),
    );
    if (!hasPermission)
      return res.status(403).json({ error: "Insufficient permissions" });
    next();
  };
}

// Usage: app.delete("/api/users/:id", authenticate, authorize("users:delete"), deleteUser);
typescript
const PERMISSIONS = {
  admin: [
    "users:read",
    "users:write",
    "users:delete",
    "posts:read",
    "posts:write",
    "posts:delete",
  ],
  editor: ["posts:read", "posts:write", "posts:delete", "users:read"],
  viewer: ["posts:read", "users:read"],
} as const;

type Role = keyof typeof PERMISSIONS;

function authorize(...requiredPermissions: string[]) {
  return (req: Request, res: Response, next: NextFunction) => {
    const userPermissions = PERMISSIONS[req.user.role as Role] || [];
    const hasPermission = requiredPermissions.every((p) =>
      (userPermissions as readonly string[]).includes(p),
    );
    if (!hasPermission)
      return res.status(403).json({ error: "Insufficient permissions" });
    next();
  };
}

// 使用示例:app.delete("/api/users/:id", authenticate, authorize("users:delete"), deleteUser);

Resource-Level Authorization

资源级授权

typescript
// WRONG: Only checking role, not ownership -- any editor can edit ANY post
// CORRECT: Check ownership or admin role
app.put(
  "/api/posts/:id",
  authenticate,
  authorize("posts:write"),
  async (req, res) => {
    const post = await db.post.findUnique({ where: { id: req.params.id } });
    if (!post) return res.status(404).json({ error: "Not found" });
    if (post.authorId !== req.user.id && req.user.role !== "admin") {
      return res
        .status(403)
        .json({ error: "Not authorized to edit this post" });
    }
    await db.post.update({ where: { id: req.params.id }, data: req.body });
  },
);
typescript
// 错误用法:仅检查角色,不验证所有权——任何编辑者都可以编辑任意帖子
// 正确用法:验证所有权或管理员角色
app.put(
  "/api/posts/:id",
  authenticate,
  authorize("posts:write"),
  async (req, res) => {
    const post = await db.post.findUnique({ where: { id: req.params.id } });
    if (!post) return res.status(404).json({ error: "Not found" });
    if (post.authorId !== req.user.id && req.user.role !== "admin") {
      return res
        .status(403)
        .json({ error: "Not authorized to edit this post" });
    }
    await db.post.update({ where: { id: req.params.id }, data: req.body });
  },
);

Password Handling

密码处理

typescript
import bcrypt from "bcrypt";
// WRONG: plaintext or MD5/SHA256 (too fast, brute-forceable)
// CORRECT: bcrypt with appropriate cost factor
const SALT_ROUNDS = 12; // ~250ms on modern hardware

async function hashPassword(password: string): Promise<string> {
  return bcrypt.hash(password, SALT_ROUNDS);
}
async function verifyPassword(
  password: string,
  hash: string,
): Promise<boolean> {
  return bcrypt.compare(password, hash); // constant-time comparison built-in
}

// Registration
await db.user.create({
  data: { email, password: await hashPassword(req.body.password) },
});

// Login -- WRONG: "Invalid password" (reveals email exists) | CORRECT: generic message
const user = await db.user.findUnique({ where: { email } });
if (!user || !(await verifyPassword(req.body.password, user.password))) {
  return res.status(401).json({ error: "Invalid email or password" });
}
typescript
import bcrypt from "bcrypt";
// 错误用法:明文存储或使用MD5/SHA256(速度过快,易被暴力破解)
// 正确用法:使用bcrypt并设置合适的成本因子
const SALT_ROUNDS = 12; // 现代硬件上约耗时250ms

async function hashPassword(password: string): Promise<string> {
  return bcrypt.hash(password, SALT_ROUNDS);
}
async function verifyPassword(
  password: string,
  hash: string,
): Promise<boolean> {
  return bcrypt.compare(password, hash); // 内置恒定时长比较
}

// 注册流程
await db.user.create({
  data: { email, password: await hashPassword(req.body.password) },
});

// 登录流程——错误用法:返回“密码错误”(暴露邮箱已存在) | 正确用法:返回通用提示信息
const user = await db.user.findUnique({ where: { email } });
if (!user || !(await verifyPassword(req.body.password, user.password))) {
  return res.status(401).json({ error: "Invalid email or password" });
}

Password Policies

密码策略

typescript
function validatePassword(password: string): string[] {
  const errors: string[] = [];
  if (password.length < 12) errors.push("Minimum 12 characters");
  if (password.length > 128) errors.push("Maximum 128 characters");

  // Check against breached password lists (haveibeenpwned API or local)
  // Do NOT enforce arbitrary complexity rules (uppercase + number + symbol)
  // NIST 800-63B recommends length over complexity
  return errors;
}
typescript
function validatePassword(password: string): string[] {
  const errors: string[] = [];
  if (password.length < 12) errors.push("最小长度12位");
  if (password.length > 128) errors.push("最大长度128位");

  // 对照泄露密码列表检查(使用haveibeenpwned API或本地列表)
  // 不要强制设置任意复杂度规则(如必须包含大写字母+数字+符号)
  // NIST 800-63B推荐优先考虑长度而非复杂度
  return errors;
}

Secrets Management

密钥管理

python
undefined
python
undefined

WRONG: Hardcoded values in source code

错误用法:在源代码中硬编码密钥

API_KEY = "some-value-here"

API_KEY = "some-value-here"

CORRECT: Environment variables loaded from .env

正确用法:从.env文件加载环境变量

from dotenv import load_dotenv import os
load_dotenv() api_key = os.getenv("API_KEY") db_url = os.getenv("DATABASE_URL")
from dotenv import load_dotenv import os
load_dotenv() api_key = os.getenv("API_KEY") db_url = os.getenv("DATABASE_URL")

CORRECT: Secrets manager for production

生产环境正确做法:使用密钥管理器

AWS: Secrets Manager, Parameter Store

AWS: Secrets Manager, Parameter Store

GCP: Secret Manager

GCP: Secret Manager

HashiCorp Vault for self-hosted

自托管场景使用HashiCorp Vault

undefined
undefined

Secret Rotation

密钥轮换

1. Generate new secret value
2. Deploy code that accepts BOTH old and new values
3. Update all consumers to use the new value
4. Verify old value is no longer in use
5. Revoke old value

Never: Rotate in-place without a transition period
1. 生成新的密钥值
2. 部署支持同时使用新旧密钥的代码
3. 更新所有消费者以使用新密钥
4. 验证旧密钥已不再被使用
5. 吊销旧密钥

绝对不要:在没有过渡周期的情况下直接原地轮换密钥

Encryption Patterns

加密模式

In Transit

传输中加密

typescript
// Redirect HTTP to HTTPS in production
app.use((req, res, next) => {
  if (
    req.headers["x-forwarded-proto"] !== "https" &&
    process.env.NODE_ENV === "production"
  ) {
    return res.redirect(301, `https://${req.hostname}${req.url}`);
  }
  next();
});
// HSTS header
app.use((req, res, next) => {
  res.setHeader(
    "Strict-Transport-Security",
    "max-age=31536000; includeSubDomains",
  );
  next();
});
typescript
// 生产环境中将HTTP重定向到HTTPS
app.use((req, res, next) => {
  if (
    req.headers["x-forwarded-proto"] !== "https" &&
    process.env.NODE_ENV === "production"
  ) {
    return res.redirect(301, `https://${req.hostname}${req.url}`);
  }
  next();
});
// 设置HSTS头
app.use((req, res, next) => {
  res.setHeader(
    "Strict-Transport-Security",
    "max-age=31536000; includeSubDomains",
  );
  next();
});

At Rest

静态数据加密

typescript
import crypto from "crypto";
const ALGORITHM = "aes-256-gcm";

function encrypt(
  plaintext: string,
  key: Buffer,
): { ciphertext: string; iv: string; tag: string } {
  const iv = crypto.randomBytes(16);
  const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
  let ciphertext =
    cipher.update(plaintext, "utf8", "hex") + cipher.final("hex");
  return {
    ciphertext,
    iv: iv.toString("hex"),
    tag: cipher.getAuthTag().toString("hex"),
  };
}

function decrypt(
  ciphertext: string,
  key: Buffer,
  iv: string,
  tag: string,
): string {
  const decipher = crypto.createDecipheriv(
    ALGORITHM,
    key,
    Buffer.from(iv, "hex"),
  );
  decipher.setAuthTag(Buffer.from(tag, "hex"));
  return decipher.update(ciphertext, "hex", "utf8") + decipher.final("utf8");
}
// Use for PII, sensitive data. Encryption key in secrets manager, NOT in code.
typescript
import crypto from "crypto";
const ALGORITHM = "aes-256-gcm";

function encrypt(
  plaintext: string,
  key: Buffer,
): { ciphertext: string; iv: string; tag: string } {
  const iv = crypto.randomBytes(16);
  const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
  let ciphertext =
    cipher.update(plaintext, "utf8", "hex") + cipher.final("hex");
  return {
    ciphertext,
    iv: iv.toString("hex"),
    tag: cipher.getAuthTag().toString("hex"),
  };
}

function decrypt(
  ciphertext: string,
  key: Buffer,
  iv: string,
  tag: string,
): string {
  const decipher = crypto.createDecipheriv(
    ALGORITHM,
    key,
    Buffer.from(iv, "hex"),
  );
  decipher.setAuthTag(Buffer.from(tag, "hex"));
  return decipher.update(ciphertext, "hex", "utf8") + decipher.final("utf8");
}
// 适用于PII、敏感数据加密。加密密钥存储在密钥管理器中,绝对不要硬编码到代码里。

CORS Configuration

CORS配置

typescript
import cors from "cors";

// WRONG: Allow everything
app.use(cors()); // origin: *, credentials: false

// WRONG: Wildcard with credentials
app.use(cors({ origin: "*", credentials: true })); // browsers reject this

// CORRECT: Explicit allowed origins
const ALLOWED_ORIGINS = [
  "https://myapp.com",
  "https://admin.myapp.com",
  ...(process.env.NODE_ENV !== "production" ? ["http://localhost:3000"] : []),
];

app.use(
  cors({
    origin: (origin, callback) => {
      if (!origin || ALLOWED_ORIGINS.includes(origin)) {
        callback(null, true);
      } else {
        callback(new Error("Not allowed by CORS"));
      }
    },
    credentials: true,
    methods: ["GET", "POST", "PUT", "PATCH", "DELETE"],
    allowedHeaders: ["Content-Type", "Authorization"],
    maxAge: 86400, // cache preflight for 24 hours
  }),
);
typescript
import cors from "cors";

// 错误用法:允许所有来源
app.use(cors()); // origin: *, credentials: false

// 错误用法:通配符与凭据同时使用
app.use(cors({ origin: "*", credentials: true })); // 浏览器会拒绝此配置

// 正确用法:显式指定允许的来源
const ALLOWED_ORIGINS = [
  "https://myapp.com",
  "https://admin.myapp.com",
  ...(process.env.NODE_ENV !== "production" ? ["http://localhost:3000"] : []),
];

app.use(
  cors({
    origin: (origin, callback) => {
      if (!origin || ALLOWED_ORIGINS.includes(origin)) {
        callback(null, true);
      } else {
        callback(new Error("Not allowed by CORS"));
      }
    },
    credentials: true,
    methods: ["GET", "POST", "PUT", "PATCH", "DELETE"],
    allowedHeaders: ["Content-Type", "Authorization"],
    maxAge: 86400, // 预检请求缓存24小时
  }),
);

Rate Limiting

速率限制

typescript
import rateLimit from "express-rate-limit";
import RedisStore from "rate-limit-redis";

// Global rate limit
app.use(
  rateLimit({
    windowMs: 15 * 60 * 1000, // 15 minutes
    max: 100, // 100 requests per window
    standardHeaders: true, // RateLimit-* headers
    legacyHeaders: false,
    store: new RedisStore({
      sendCommand: (...args) => redisClient.sendCommand(args),
    }),
  }),
);

// Strict limit on auth endpoints
app.use(
  "/api/auth/login",
  rateLimit({
    windowMs: 15 * 60 * 1000,
    max: 5, // 5 login attempts per 15 min
    message: { error: "Too many login attempts. Try again later." },
  }),
);

// Per-API-key rate limiting for developer APIs
app.use(
  "/api/v1/",
  rateLimit({
    windowMs: 60 * 1000, // 1 minute
    max: 60, // 60 requests per minute
    keyGenerator: (req) => req.apiClient?.id || req.ip,
  }),
);
typescript
import rateLimit from "express-rate-limit";
import RedisStore from "rate-limit-redis";

// 全局速率限制
app.use(
  rateLimit({
    windowMs: 15 * 60 * 1000, // 15分钟
    max: 100, // 每个窗口最多100次请求
    standardHeaders: true, // 返回RateLimit-*系列响应头
    legacyHeaders: false,
    store: new RedisStore({
      sendCommand: (...args) => redisClient.sendCommand(args),
    }),
  }),
);

// 对认证端点设置严格限制
app.use(
  "/api/auth/login",
  rateLimit({
    windowMs: 15 * 60 * 1000,
    max: 5, // 15分钟内最多5次登录尝试
    message: { error: "Too many login attempts. Try again later." },
  }),
);

// 为开发者API设置基于API密钥的速率限制
app.use(
  "/api/v1/",
  rateLimit({
    windowMs: 60 * 1000, // 1分钟
    max: 60, // 每分钟最多60次请求
    keyGenerator: (req) => req.apiClient?.id || req.ip,
  }),
);

Security Headers

安全响应头

typescript
import helmet from "helmet";

app.use(helmet()); // Sets many secure headers at once

// Key headers helmet sets:
// X-Content-Type-Options: nosniff
// X-Frame-Options: DENY
// Strict-Transport-Security: max-age=15552000; includeSubDomains
// Content-Security-Policy: default-src 'self'

// Customize CSP for your app
app.use(
  helmet.contentSecurityPolicy({
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", "data:", "https://cdn.example.com"],
      connectSrc: ["'self'", "https://api.example.com"],
    },
  }),
);
typescript
import helmet from "helmet";

app.use(helmet()); // 一次性设置多个安全响应头

// Helmet设置的关键响应头:
// X-Content-Type-Options: nosniff
// X-Frame-Options: DENY
// Strict-Transport-Security: max-age=15552000; includeSubDomains
// Content-Security-Policy: default-src 'self'

// 根据应用需求自定义CSP
app.use(
  helmet.contentSecurityPolicy({
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", "data:", "https://cdn.example.com"],
      connectSrc: ["'self'", "https://api.example.com"],
    },
  }),
);

Input Validation

输入验证

typescript
import { z } from "zod";

// WRONG: Trusting user input directly (SQL injection risk)
app.post("/api/users", (req, res) => {
  db.query(`SELECT * FROM users WHERE email = '${req.body.email}'`);
});

// CORRECT: Validate with schema, use parameterized queries
const CreateUserSchema = z.object({
  email: z.string().email().max(255),
  name: z.string().min(1).max(100).trim(),
  age: z.number().int().min(13).max(150).optional(),
});

app.post("/api/users", async (req, res) => {
  const result = CreateUserSchema.safeParse(req.body);
  if (!result.success) {
    return res.status(400).json({ errors: result.error.flatten() });
  }
  // Use parameterized query (ORM or prepared statement)
  await db.user.create({ data: result.data });
});
typescript
import { z } from "zod";

// 错误用法:直接信任用户输入(存在SQL注入风险)
app.post("/api/users", (req, res) => {
  db.query(`SELECT * FROM users WHERE email = '${req.body.email}'`);
});

// 正确用法:使用Schema验证,使用参数化查询
const CreateUserSchema = z.object({
  email: z.string().email().max(255),
  name: z.string().min(1).max(100).trim(),
  age: z.number().int().min(13).max(150).optional(),
});

app.post("/api/users", async (req, res) => {
  const result = CreateUserSchema.safeParse(req.body);
  if (!result.success) {
    return res.status(400).json({ errors: result.error.flatten() });
  }
  // 使用参数化查询(ORM或预编译语句)
  await db.user.create({ data: result.data });
});

Common Anti-Patterns Summary

常见反模式汇总

AVOID                              DO INSTEAD
-------------------------------------------------------------------
JWT in localStorage                httpOnly secure cookie (refresh), memory (access)
MD5/SHA for passwords              bcrypt or argon2 with proper cost factor
Hardcoded secrets in code          Environment variables + secrets manager
cors({ origin: '*' })             Explicit allowed origins list
"Invalid password" message         "Invalid email or password" (no enumeration)
No rate limiting on auth           Strict rate limits on login/register
Rolling your own crypto            Use established libraries (jose, bcrypt)
Trusting user input                Validate with zod/joi, parameterized queries
Same API key forever               Rotate keys regularly, support multiple active
No HTTPS redirect                  Force HTTPS + HSTS header
Symmetric JWT for multi-service    Use RS256/ES256 (asymmetric) for distributed
No input length limits             Max length on all string inputs
需避免的做法                          正确做法
-------------------------------------------------------------------
在localStorage中存储JWT               refresh令牌存入httpOnly安全Cookie,access令牌存入内存
使用MD5/SHA哈希存储密码              使用bcrypt或argon2并设置合适的成本因子
在代码中硬编码密钥                  使用环境变量+密钥管理器
cors({ origin: '*' })                显式指定允许的来源列表
返回“密码错误”提示信息               返回“邮箱或密码错误”的通用提示(避免枚举攻击)
不对认证端点设置速率限制              对登录/注册端点设置严格的速率限制
自行实现加密逻辑                    使用成熟的库(如jose、bcrypt)
信任用户输入                        使用zod/joi验证,使用参数化查询
永久使用同一个API密钥               定期轮换密钥,支持同时使用多个有效密钥
不设置HTTPS重定向                    强制HTTPS + HSTS头
多服务场景使用对称加密JWT            分布式场景使用RS256/ES256(非对称加密)JWT
不设置输入长度限制                  对所有字符串输入设置最大长度