×
¥
查看详情
🔥 会员专享 文生代码 其它

安全编码规范

👁️ 557 次查看
📅 Nov 24, 2025
💡 核心价值: 针对指定语言框架和敏感操作,提供安全编码最佳实践与防护建议,确保应用在设计和实现阶段符合安全规范,帮助开发者预防漏洞、提升代码安全性,并支持快速应用到实际开发场景。

🎯 可自定义参数(3个)

编程语言/框架
应用开发使用的编程语言或框架
敏感操作类型
需要安全防护的操作或功能类型
安全目标/关注点
希望防护或优化的安全方向

🎨 效果示例

以下内容面向 Java 17 / Spring Boot 3(Spring Security 6)应用,在“用户登录与会话管理、密码存储与CSRF”三个关键环节给出可落地的安全设计与实现建议,重点覆盖:Argon2id+盐、强密码策略、登录限速与验证码、2FA、多处会话防护(固定、Cookie 属性、并发、注销)、CSRF 令牌与双重提交、统一错误提示、审计日志与告警。

一、密码存储:Argon2id + 唯一盐 + 可选 pepper

  • 使用算法:Argon2id(抗 GPU/侧信道,兼顾密码猜测与内存强度)。
  • 每个账户独立的随机盐(≥16字节),存储在哈希中;不要复用盐。
  • 可选 pepper(应用级密钥,存于环境变量/密钥管理系统,而非数据库),拼接在密码末尾后再做哈希;便于泄露时全局失效。
  • Spring Security 实现
    • 依赖(确保 BouncyCastle 可用):
      • Maven: org.bouncycastle:bcprov-jdk18on
    • Argon2id 参数建议(按照 OWASP/硬件调优):
      • saltLen=16, hashLen=32, memory=65536 KiB(64MB)或更高,iterations=3–5,parallelism=1–2。上线前用基准测试控制单次耗时≈100–250ms。
    • Bean 示例:
      • @Bean PasswordEncoder passwordEncoder() { return new Argon2PasswordEncoder(16, 32, 1, 65536, 4); }
      • 如需 pepper,可自定义包装器,在 encode 时对原始密码进行 NFKC 归一化并拼接 pepper 再调用 Argon2。
  • 升级/兼容:使用 DelegatingPasswordEncoder 支持算法平滑迁移;登录成功后对旧算法哈希执行“透明重哈希”。
  • 强密码策略(服务端校验)
    • 长度优先:最短 12–14,鼓励 20+ 的短语;最大长度限制 64–128 以防止 DoS。
    • 字符集不强制复杂度,但拒绝常见/泄露密码(如 HIBP k-匿名查询)与包含用户名/邮箱等。
    • Unicode 正规化(NFKC),去除不可见控制字符;不要静默截断。
    • 使用 passay 或自研规则做策略校验;任何失败信息仅返回“密码不符合策略要求”。

二、登录安全:限速/验证码、统一错误、2FA

  • 统一错误提示与枚举防护
    • 所有登录失败返回相同消息/状态码(如 401/“用户名或密码不正确”);不要区分“用户不存在/锁定/密码错误/2FA 未启用”。
    • 登录表单与找回密码接口均避免用户名枚举(对存在与否都返回相同提示)。
  • 限速与渐进封锁
    • 维度:IP、账号、IP+账号组合同时限速,防止绕过。
    • 策略:例如 5 次失败/10 分钟触发 15 分钟冷却;采用指数退避;对高风险 IP 更严。
    • 技术实现:Bucket4j/Redis 限流。登录成功应重置计数。
    • 达到阈值后强制验证码(hCaptcha/reCAPTCHA),并在后端验证票据。
  • 2FA(推荐 TOTP/WebAuthn;SMS 仅作兜底)
    • TOTP(基于时间一次性口令)
      • 库:com.warrenstrange:googleauth 或 webauthn4j(用于 FIDO2)。
      • 秘钥保管:随机 160 位以上 Base32,存储加密(或密钥托管)+ 单向校验;启用/禁用动作需要二次验证。
      • 校验窗口:±1 时间步(30s),防重放(记录上次使用的 time-step)。
      • 恢复码:生成 8–10 个 10 位随机码,单向哈希存储,单次使用即失效。
      • 流程:先验证密码,再进入 2FA 步骤;通过后在会话中标记 MFA_VERIFIED 并设置较短的 2FA 会话 TTL。
    • WebAuthn(最好实践)
      • 支持无密码/多因素,抗钓鱼;对敏感操作做 Step-up 验证。
  • Spring Security 流程建议
    • 第一步用户名密码认证通过后,不直接授予完整权限;转入 2FA 视图或 API;通过后再授予业务角色。
    • 对敏感操作(转账、改密)强制短期 2FA(可复用已验证标记并设置较短 TTL)。

三、会话管理:固定攻击、Cookie、并发与注销

  • Session 固定攻击防护
    • 登录成功后 rotate 会话 ID(Spring Security 默认:sessionFixation().migrateSession/changeSessionId)。
    • 登出时 invalidate session + 清理所有认证相关 Cookie/缓存标记。
  • Cookie 安全属性
    • Session Cookie:Secure=true、HttpOnly=true、SameSite=Lax(表单登录、常规跨站导航仍可用;如必须跨站回跳用 OAuth/State 参数)。
    • 对“记住我”或 XSRF 令牌分别设置合理 SameSite:记住我建议 Lax;XSRF-Token 需 JS 可读则 HttpOnly=false 但仍 Secure+SameSite=Lax。
    • 禁止在 URL 中传递会话标识;仅允许 Cookie。
  • 会话超时与并发
    • 空闲超时 15–30 分钟;“记住我”仅保持登录态但仍需 2FA 才能做敏感操作。
    • 可选配置最大并发会话数(如每用户 5)并记录异常异地登录。
  • 后端会话存储
    • Spring Session(Redis/JDBC)强化分布式会话;设置 TTL 与索引。
    • 序列化安全:使用 JSON(GenericJackson2JsonRedisSerializer),禁用默认反序列化白名单以防反序列化漏洞。

四、CSRF 防护:令牌、双重提交、来源校验

  • 服务器端渲染表单(MVC)
    • Spring Security 默认开启 CSRF;在表单内包含隐藏字段 _csrf。
    • 登录、登出、状态变更接口均需要 CSRF 令牌。
  • SPA/前后端分离(基于 Cookie 的会话)
    • 使用 CookieCsrfTokenRepository + 双重提交:服务端发放可读的 XSRF-TOKEN(HttpOnly=false, Secure, SameSite=Lax),前端每次以 X-XSRF-TOKEN 头回传。
    • 辅以 Origin/Referer 校验(仅允许本站/受信域)。
  • Stateless API 特例
    • 若完全无 Cookie(纯 Bearer Token),可按需禁用 CSRF,但需确保 Authorization 仅在 HTTPS 且 CORS 严格白名单,不使用通配“*”,并禁止跨站携带凭证。
  • 令牌轮换
    • 在登录成功/会话迁移后重新生成 CSRF 令牌;Spring Security 在新会话下会分配新令牌。

五、关键配置与代码示例(Spring Boot 3/Spring Security 6)

  • application.yml(会话与 Cookie)
    • server: servlet: session: timeout: 30m cookie: secure: true http-only: true same-site: lax
  • SecurityFilterChain(核心安全头、会话、CSRF、HTTPS)
    • @Bean SecurityFilterChain security(HttpSecurity http) throws Exception { CookieCsrfTokenRepository repo = CookieCsrfTokenRepository.withHttpOnlyFalse(); repo.setCookieCustomizer(builder -> builder.secure(true).sameSite("Lax").path("/")); http .requiresChannel(ch -> ch.anyRequest().requiresSecure()) .csrf(csrf -> csrf.csrfTokenRepository(repo)) .sessionManagement(sm -> sm .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) .sessionFixation(sf -> sf.migrateSession())) .headers(h -> h .httpStrictTransportSecurity(hsts -> hsts.includeSubDomains(true).preload(true).maxAgeInSeconds(31536000)) .contentSecurityPolicy(csp -> csp.policyDirectives("default-src 'self'")) .frameOptions(fo -> fo.deny())) .logout(lo -> lo.invalidateHttpSession(true).deleteCookies("JSESSIONID")) .authorizeHttpRequests(auth -> auth .requestMatchers(HttpMethod.GET, "/login", "/assets/**").permitAll() .anyRequest().authenticated()); return http.build(); }
  • Argon2id PasswordEncoder
    • @Bean PasswordEncoder passwordEncoder() { return new Argon2PasswordEncoder(16, 32, 1, 65536, 4); // 64MB, 4 次迭代,按硬件调优 }
  • MVC 表单(Thymeleaf)加入 CSRF
  • 认证事件审计(统一成功/失败日志)
    • @Component class AuthEvents { private static final Logger log = LoggerFactory.getLogger(AuthEvents.class); @EventListener public void onSuccess(AuthenticationSuccessEvent e) { var auth = e.getAuthentication(); log.info("auth_success user={} ip={} ua={}", auth.getName(), getClientIp(), getUserAgent()); } @EventListener public void onFailure(AbstractAuthenticationFailureEvent e) { var name = e.getAuthentication().getName(); log.warn("auth_failure user={} ip={} reason={}", name, getClientIp(), e.getException().getClass().getSimpleName()); } }
  • 限速(Bucket4j 简例,按 IP+用户名)
    • Bucket resolveBucket(String key) { /* 从缓存取或新建固定窗口/令牌桶 */ } @PostMapping("/login") public ResponseEntity<?> login(@RequestBody LoginDto req) { String key = req.username()+"|"+clientIp(); if (!resolveBucket(key).tryConsume(1)) { return ResponseEntity.status(429).build(); } // 正常认证流程… }
  • 2FA(TOTP 核心)
    • GoogleAuthenticator gauth = new GoogleAuthenticator(); boolean ok = gauth.authorize(user.getTotpSecret(), code); if (ok) { session.setAttribute("MFA_VERIFIED", true); }
    • 存储 totpSecret 时加密;恢复码用 Argon2id 哈希保存。

六、CSRF 与 CORS 配置注意

  • 跨站请求必须精确白名单域名,允许凭证时不得使用“*”;仅允许需要的方法与头。
  • 后端对 Login/State-Change 请求同时验证 CSRF 令牌与 Origin/Referer。
  • XSRF-TOKEN Cookie 必须 Secure + SameSite=Lax;仅在需要前端读取时将 HttpOnly=false。

七、日志审计与安全告警

  • 记录内容(结构化 JSON,避免敏感数据)
    • 登录成功/失败、2FA 成功/失败、登出、密码/邮箱变更、会话创建/失效。
    • 字段:用户ID、结果、IP、UA、请求ID、失败原因类型、地理位置(可选)。
  • 不记录:口令、2FA 代码、完整令牌、Cookie 值。
  • 告警规则
    • 多次失败、短时间多 IP 命中同账号、异地异常、2FA 多次失败、可疑 Referer/Origin。
    • 关键账户(管理员)登录异动实时告警。
  • 日志保护:按合规(例如 1–2 年)留存,最小权限,只可追加,定期轮转与完整性校验;接入 SIEM。

八、其他防护要点(设计阶段即落实)

  • 全站强制 HTTPS + HSTS(含子域,支持 preload)。
  • 记住我 Token 使用随机 256 位值,数据库仅存其哈希;成功使用后旋转 Token。
  • 密码重置流程同样执行枚举防护与速率限制;链接一次有效并设置最短有效期(如 10–15 分钟),绑定设备/IP 指纹可选。
  • 错误码与消息对齐:接口返回一致的结构化错误,避免侧信道。

九、上线前检查清单(摘要)

  • Argon2id 参数经基准测试,CPU/内存开销符合预算;盐随机、唯一;可选 pepper 配置到安全密管。
  • 密码策略与泄露库校验已启用;归一化与长度边界明确。
  • 登录限速(IP/账号/组合)与验证码联动;2FA 已覆盖敏感操作。
  • 会话固定防护、Cookie Secure/HttpOnly/SameSite=Lax、生存期、并发控制、注销清理正确。
  • CSRF(表单/SPA 双重提交)+ Origin/Referer 校验;令牌在登录/会话迁移后轮换。
  • 审计日志完整、无敏感信息;告警规则生效;与 SIEM/告警渠道联通。
  • CORS 精确白名单;不使用通配;仅暴露必要方法/头;允许凭证时禁止“*”。

以上实践可直接在 Spring Boot 3/Spring Security 6 项目中落地,覆盖登录与会话管理、密码存储与 CSRF 的核心安全目标,减少被撞库、暴力破解、会话固定、跨站请求伪造等常见风险。

下面给出针对 Node.js 20 / Express 4 的多文件上传与图片处理的安全设计与实现建议,覆盖扩展名白名单、Magic Number 校验、大小/速率限制、隔离存储、随机文件名、杀毒扫描钩子、图像库安全处理、抗 DOS、最小权限、签名直传与回调验签等关键点。目标是让应用在设计与实现阶段即符合安全规范。

一、总体安全设计与数据流

  • 上传入口仅支持图片类文件,且采用流式处理,尽早拒绝非法内容。
  • 多重校验:扩展名白名单 + Magic Number/MIME 真值校验 + 尺寸/像素限制。
  • 两段存储:先落地到隔离的“暂存/隔离区”,完成杀毒与类型验证后,再搬运到“正式存储区”(不可执行、不可直接静态暴露)。
  • 随机文件名及路径分片,避免预测与枚举。
  • 图像处理采用安全库(如 sharp),限制像素/内存消耗,剥离元数据,限制格式。
  • 防 DOS:限流、限并发、限速、限总大小及单文件大小,尽早中止。
  • 最小权限:应用用户仅可写入上传目录;不以 root 运行;存储目录挂载 noexec,nodev,nosuid。
  • 云直传:通过后端生成短时有效的签名直传凭证;回调验签并二次校验对象信息及完成杀毒。

二、允许类型与白名单策略

  • 建议仅允许:jpg/jpeg、png、gif、webp、avif。默认不接收 svg(可嵌脚本,易导致 XSS),如确需支持则仅作为下载附件且经严格净化,不内联展示。
  • 扩展名只做初筛,不作为最终信任;实际类型以 Magic Number/MIME 真值为准。
  • 返回下载或展示时设置 X-Content-Type-Options: nosniff,防止浏览器 MIME 嗅探。

三、Magic Number/MIME 真值校验

  • 使用 file-type 或 magic-bytes 库对首 N 字节进行检测,拒绝与白名单不匹配的类型。
  • 校验顺序:扩展名初筛 → 读取前 16KB → Magic Number 检测 → 与白名单映射比对 → 方可继续写盘或处理。
  • 不信任客户端提供的 Content-Type。

示例关键代码(Busboy + file-type + 隔离存储 + 随机文件名)

  • 说明:使用 Busboy 实现流式多文件上传;将文件先写到隔离区;首段字节足够后进行类型检测;失败立即中止并清理。

const express = require('express'); const Busboy = require('busboy'); const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); const { fileTypeFromBuffer } = require('file-type');

const app = express();

// 基本安全头 const helmet = require('helmet'); app.use(helmet({ contentSecurityPolicy: false, crossOriginResourcePolicy: { policy: 'same-site' } })); app.disable('x-powered-by');

// 限流与速率限制(抗DOS) const rateLimit = require('express-rate-limit'); app.use('/upload', rateLimit({ windowMs: 60 * 1000, max: 30, // 每分钟每IP最多30次 standardHeaders: true, legacyHeaders: false }));

// 允许的真实 MIME 映射及扩展名 const ALLOWED = { 'image/jpeg': 'jpg', 'image/png': 'png', 'image/gif': 'gif', 'image/webp': 'webp', 'image/avif': 'avif' }; const MAX_FILE_SIZE = 10 * 1024 * 1024; // 单文件最大10MB const MAX_FILES = 10; // 每请求最多10个文件 const QUARANTINE_DIR = '/var/app/uploads/quarantine'; // 挂载noexec const FINAL_DIR = '/var/app/uploads/images'; // 挂载noexec

function safeJoin(base, target) { const p = path.resolve(base, target); if (!p.startsWith(path.resolve(base) + path.sep)) throw new Error('Path traversal'); return p; }

function randomName(ext) { return crypto.randomUUID().replace(/-/g, '') + (ext ? '.' + ext : ''); }

app.post('/upload', (req, res) => { const busboy = Busboy({ headers: req.headers, limits: { files: MAX_FILES, fileSize: MAX_FILE_SIZE, parts: MAX_FILES + 5, } });

const results = []; let aborted = false;

busboy.on('file', (fieldname, file, filename, encoding, mime) => { if (aborted) return file.resume();

// 扩展名初筛(不信任,但可早拒)
const ext = (path.extname(filename || '').toLowerCase() || '').replace('.', '');
const extOk = Object.values(ALLOWED).includes(ext);
// 预先缓存前16KB用于Magic Number检测
const sniffChunks = [];
let sniffSize = 0;
let typeDetected = null;
let tmpPath = null;
let writeStream = null;

file.on('limit', () => {
  aborted = true;
  file.unpipe(writeStream);
  writeStream && writeStream.destroy();
  tmpPath && fs.unlink(tmpPath, () => {});
  res.status(413).json({ error: 'File too large' });
});

file.on('data', async (chunk) => {
  if (aborted) return;
  if (sniffSize < 16 * 1024) {
    sniffChunks.push(chunk);
    sniffSize += chunk.length;
    if (!typeDetected && sniffSize >= 1024) {
      const buf = Buffer.concat(sniffChunks);
      const ft = await fileTypeFromBuffer(buf).catch(() => null);
      if (!ft || !ALLOWED[ft.mime]) {
        aborted = true;
        file.resume();
        res.status(415).json({ error: 'Unsupported file type' });
        return;
      }
      typeDetected = ft;
      // 二次比对扩展名(仅作为加分项)
      if (extOk && ext !== ALLOWED[ft.mime]) {
        // 扩展名与真实类型不一致:以真实类型为准,重命名
      }

      // 建立隔离区文件
      const safeName = randomName(ALLOWED[ft.mime]);
      const rel = path.join(crypto.randomUUID().slice(0,2), safeName); // 路径分片
      tmpPath = safeJoin(QUARANTINE_DIR, rel);

      // 确保目录存在
      fs.mkdirSync(path.dirname(tmpPath), { recursive: true, mode: 0o700 });

      // 使用 O_NOFOLLOW 防止符号链接
      const fd = fs.openSync(tmpPath, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY | fs.constants.O_NOFOLLOW, 0o600);
      writeStream = fs.createWriteStream('', { fd });
      // 将已嗅到的字节写入
      writeStream.write(buf);
    }
  } else {
    if (writeStream) writeStream.write(chunk);
  }
});

file.on('end', async () => {
  if (aborted) return;
  if (!typeDetected || !writeStream) {
    aborted = true;
    res.status(400).json({ error: 'Upload failed or empty file' });
    return;
  }
  writeStream.end();

  // 计算哈希用于去重/审计
  const hash = await new Promise((resolve, reject) => {
    const h = crypto.createHash('sha256');
    const rs = fs.createReadStream(tmpPath);
    rs.on('data', d => h.update(d));
    rs.on('end', () => resolve(h.digest('hex')));
    rs.on('error', reject);
  });

  // 杀毒扫描
  const clean = await avScan(tmpPath).catch(() => false);
  if (!clean) {
    fs.unlink(tmpPath, () => {});
    results.push({ fieldname, error: 'Malware detected' });
    return;
  }

  // 搬运到正式存储(不可执行目录)
  const finalRel = path.join('images', hash.slice(0,2), randomName(ALLOWED[typeDetected.mime]));
  const finalPath = safeJoin(FINAL_DIR, finalRel);
  fs.mkdirSync(path.dirname(finalPath), { recursive: true, mode: 0o700 });
  fs.renameSync(tmpPath, finalPath);
  fs.chmodSync(finalPath, 0o600);

  results.push({
    fieldname,
    mime: typeDetected.mime,
    size: fs.statSync(finalPath).size,
    hash,
    path: finalRel // 存储相对路径,不向客户端暴露真实文件系统路径
  });
});

});

busboy.on('finish', () => { if (!aborted) res.json({ files: results }); });

req.pipe(busboy); });

// 示例杀毒扫描钩子(ClamAV) async function avScan(filePath) { // 生产环境建议使用常驻 clamd,提高性能 // 伪代码:可使用 'clamdjs' 或调用 clamdscan return await new Promise((resolve) => { const { spawn } = require('child_process'); const p = spawn('clamdscan', ['--no-summary', filePath], { stdio: 'ignore' }); p.on('exit', (code) => resolve(code === 0)); p.on('error', () => resolve(false)); }); }

四、图像处理安全要点(sharp)

  • 固定允许的输入类型,拒绝 SVG 等潜在脚本载体。
  • 限制像素上限,防止“解压炸弹”型大维度图片耗尽内存。sharp.limitInputPixels(80_000_000)(示例上限 80MP,根据容量调整)。
  • 采用流式处理,避免一次性加载到内存;并设置并发和缓存限额(如 sharp.concurrency(2);sharp.cache({ files: 0 }))。
  • 在处理时统一重采样与尺寸限制,如仅生成最大边 4096 px 的版本,fit: 'inside',防止巨大输出。
  • 不保留元数据与附加信息(不调用 withMetadata),避免地理位置信息泄露;如需保留仅保留安全子集。
  • 转换输出统一到安全格式(如 webp 或 jpeg),并设置合理质量与 chromaSubsampling。
  • 所有处理运行在隔离文件上,完成后写入正式存储目录。

示例处理片段(将隔离区文件管道到 sharp,生成缩略图与标准图): const sharp = require('sharp'); async function processImage(srcPath, destBaseDir, mime) { const base = path.join(destBaseDir, 'processed'); fs.mkdirSync(base, { recursive: true, mode: 0o700 });

sharp.limitInputPixels(80_000_000); const img = sharp(srcPath, { sequentialRead: true }); const extOut = (mime === 'image/png') ? 'png' : 'webp'; const standard = path.join(base, randomName(extOut)); const thumb = path.join(base, randomName(extOut));

await img.clone().rotate().resize({ width: 2048, height: 2048, fit: 'inside', withoutEnlargement: true }) .toFormat(extOut, { quality: 80 }) .toFile(standard);

await img.clone().rotate().resize({ width: 320, height: 320, fit: 'cover' }) .toFormat(extOut, { quality: 70 }) .toFile(thumb);

fs.chmodSync(standard, 0o600); fs.chmodSync(thumb, 0o600); return { standard, thumb }; }

五、抗 DOS 与资源限制

  • 应用层限流:express-rate-limit;对上传路由设置更严格的速率与并发。
  • 协议层限制:Busboy limits.files、limits.fileSize、limits.parts;文件过大立即终止。
  • 连接层超时:设置 server.requestTimeout、headersTimeout,防止 slowloris。
  • 反向代理限制:在 Nginx/Envoy 前置 client_max_body_size、限速/连接数限制。
  • 队列与并发:上传与处理任务进入队列,限制并发处理数;必要时设置进程级内存与 CPU 限额(容器 cgroups)。

六、存储策略与不可执行隔离

  • 上传暂存与正式存储目录置于应用根之外,避免被静态中间件直接暴露。
  • Linux 挂载选项:noexec,nodev,nosuid;目录权限 0700;文件权限 0600。
  • 不返回真实文件系统路径,访问通过受控下载或签名 URL;下载接口设置 Content-Disposition: attachment;为图片展示仅允许安全类型。
  • 严格防路径穿越:永远使用服务器生成文件名;写入时使用 O_NOFOLLOW;写前 lstat 检查是否符号链接。

七、随机文件名与可预测性防护

  • 使用 crypto.randomUUID 或等强随机生成文件名;不使用用户提供文件名存储。
  • 路径分片(如按哈希前缀或随机前缀建多层目录),降低单目录文件数与枚举风险。
  • 记录文件哈希(sha256)用于去重与审计。

八、最小权限与运行环境

  • Node 进程不以 root 运行;应用用户仅对上传目录有写权限;对其它目录只读或无权限。
  • 容器化时使用非 root 用户,启用 seccomp/AppArmor;文件系统默认只读,上传目录为单独可写挂载点。
  • 依赖库版本固定并及时更新安全修复;启用 npm audit 并加锁 package-lock.json。
  • 启用安全头与通用防护:helmet、CORS 最小化、关闭 x-powered-by。

九、签名直传与回调验签(S3 示例)

  • 服务器端生成短时有效的预签名 POST(或 PUT)策略,仅允许白名单 Content-Type,限制 content-length-range。
  • key 使用服务器生成的随机文件名路径;客户端直传到 S3,降低应用带宽与内存压力。
  • 成功后由客户端调用你的后端回调接口,或通过 S3 事件(如 SQS/SNS/Lambda)通知后端。后端需:
    1. 验证回调请求的 HMAC 签名(以服务器下发给客户端的一次性 token 或 nonce 计算),校验时间戳与重放保护。
    2. 通过 S3 HeadObject 获取对象真实 Content-Type、Content-Length、ETag,与预期匹配;拒绝不一致或超出限制。
    3. 将对象从暂存桶或前缀移动到正式前缀(或复制),并进行杀毒与类型二次验证;验证完成后才入库可用。
    4. 回调接口需具备幂等性,使用上传事务ID或对象key+ETag作为幂等键。

示例生成预签名(简化,使用 AWS SDK v3): import { S3Client, CreatePresignedPost } from '@aws-sdk/s3-presigned-post'; const s3 = new S3Client({ region: 'us-east-1' }); app.post('/uploads/presign', async (req, res) => { // 需鉴权,校验用户配额 const uuid = crypto.randomUUID().replace(/-/g, ''); const key = quarantine/${uuid}.webp; // 服务器决定扩展名或允许客户端传递受限集合 const { url, fields } = await CreatePresignedPost(s3, { Bucket: process.env.BUCKET, Key: key, Conditions: [ ['content-length-range', 0, MAX_FILE_SIZE], ['eq', '$Content-Type', 'image/webp'] ], Expires: 300 // 5分钟 }); const nonce = crypto.randomBytes(16).toString('hex'); const hmacSecret = await saveOneTimeSecretForUser(req.user.id, key, nonce); // 用于回调验签 res.json({ url, fields, key, nonce }); });

回调验签示例(简化): app.post('/uploads/complete', express.json(), async (req, res) => { const { key, etag, nonce, sig } = req.body; const secret = await loadOneTimeSecret(key, nonce); if (!secret) return res.status(400).json({ error: 'Invalid nonce' }); const mac = crypto.createHmac('sha256', secret).update(${key}|${etag}|${nonce}).digest('hex'); if (!crypto.timingSafeEqual(Buffer.from(mac), Buffer.from(sig))) { return res.status(401).json({ error: 'Bad signature' }); } // 读取 S3 元数据二次校验,执行杀毒、类型检查、搬运至正式前缀 // 幂等处理:检查是否已完成 res.json({ ok: true }); });

十、日志与审计

  • 记录上传时间、用户ID、IP、文件哈希、检测到的真实类型、大小、处理结果。
  • 对于被拒绝或检出恶意的文件,记录原因与样本路径(必要时隔离保存以便分析)。
  • 建立指标与报警:异常高的拒绝率、失败率、超限率、单IP异常请求。

十一、配置与默认值建议

  • 服务器超时:requestTimeout 30s、headersTimeout 15s。
  • Node 全局 umask 027;上传目录 0700;文件 0600。
  • 关闭对上传路由的 gzip/压缩(避免压缩炸弹带来的 CPU 开销)。

十二、常见陷阱与补充

  • 不要信任 req.file.mimetype 或 Content-Type;只信 Magic Number。
  • 不要在处理前把文件完整读入内存;必须流式并尽早拒绝。
  • 不要将上传目录暴露给静态中间件;不要直接使用用户文件名。
  • 小心 SVG 与嵌入的 ICC/EXIF:默认剥离元数据,SVG 仅作附件下载。
  • sharp 与底层 libvips 需及时升级,关注安全通告;对输入设置严格限制。
  • 对云存储直传,确保策略仅允许白名单类型与大小范围;凭证短时有效。

按以上原则与示例实现,可在 Node.js 20 / Express 4 环境下实现多文件上传与图片处理的安全管控,满足扩展名白名单、Magic Number 校验、大小/速率限制、隔离存储、随机文件名、杀毒扫描钩子、图像库安全处理、抗 DOS、最小权限与签名直传/回调验签等安全目标。

以下建议面向 Python 3.11 / FastAPI,重点围绕数据库读写与查询构建,防止注入与信息泄露,涵盖参数化查询/ORM、最小权限账户、事务与超时、连接池控制、输出编码与转义、防盲注与时间差、统一错误处理、审计追踪、凭证管理等安全目标。

一、数据访问层设计与参数化

  • 使用 ORM/Query Builder 优先:SQLAlchemy 2.0(建议 async + psycopg3/asyncpg)。所有条件、值通过绑定参数传递,避免字符串拼接或 f-string。
  • 原生 SQL 场景:使用 sqlalchemy.text 并显式 bindparam;绝不拼接用户输入到 SQL 字符串或标识符。
  • 动态列/排序白名单:将客户端可选的列名映射到 ORM 模型属性,不接受任意字符串作为列或表名。
    • 示例:sort_by only in {"created_at": Model.created_at, "name": Model.name}。
  • IN 列表安全绑定:使用 column.in_(list) 或 bindparam(expanding=True)。
  • LIKE/搜索:使用参数绑定并限制通配符,必要时转义 % 和 _,避免构造正则或函数调用注入。
  • 防二次注入:持久化数据在再次拼接到 SQL 时同样使用参数化;不要依据“已验证/已存储”放松约束。

示例(SQLAlchemy 2.0 async,参数化与白名单):

  • 初始化引擎与会话(凭证从环境/密管)
    • from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
    • engine = create_async_engine(db_url, pool_size=10, max_overflow=5, pool_timeout=30, pool_recycle=1800, pool_pre_ping=True, echo=False)
    • AsyncSessionFactory = async_sessionmaker(engine, expire_on_commit=False)
  • 依赖注入(按请求创建会话,GET 只读事务)
    • async def get_session(): async with AsyncSessionFactory() as session: yield session
  • 查询:选择列白名单和参数
    • cols = {"created_at": Model.created_at, "name": Model.name}
    • order_col = cols.get(sort_by, Model.created_at)
    • stmt = select(Model).where(Model.owner_id == owner_id, Model.status == status).order_by(order_col.desc() if desc else order_col.asc()).limit(limit).offset(offset)
    • rows = (await session.execute(stmt)).scalars().all()
  • 原生 SQL(text + bindparam)
    • from sqlalchemy import text, bindparam
    • stmt = text("SELECT id, name FROM items WHERE owner_id=:owner_id AND status=:status").bindparams(bindparam("owner_id"), bindparam("status"))
    • result = await session.execute(stmt, {"owner_id": owner_id, "status": status})

二、最小权限账户(数据库层)

  • 为应用创建专用角色,按功能分离:app_rw(有限写)、app_ro(只读)。不要使用超级用户或数据库拥有者账户。
  • 仅授予必要 schema/table 的 SELECT/INSERT/UPDATE/DELETE;为 GET 使用 app_ro;为变更使用 app_rw。
  • 撤销默认 PUBLIC 权限;配置默认权限(ALTER DEFAULT PRIVILEGES)确保未来对象权限一致。
  • 只读事务:对查询端点设置 SET TRANSACTION READ ONLY,阻止意外写操作。
  • 限制危险操作:禁用 DROP/ALTER 等;若需管理迁移,使用单独的 CI/CD 角色。

三、事务、隔离级别与超时

  • 每个请求内显式事务管理,避免长事务:
    • GET:async with session.begin(): 先执行 SET LOCAL statement_timeout=…;SET TRANSACTION READ ONLY。
    • 写操作:短事务,隔离级别 READ COMMITTED 或 REPEATABLE READ(视业务),设置语句超时防止锁等待和时间盲注。
  • 语句/命令超时:
    • PostgreSQL:SET LOCAL statement_timeout='2s';驱动层 asyncpg command_timeout;psycopg rows_timeout。
    • 应用层超时:asyncio.timeout 防止请求卡住;设置 FastAPI/ASGI 超时中间件或网关超时。
  • 限制返回数据量:分页、limit 上限;避免一次性返回大量数据导致时间侧信道暴露。

四、连接池安全与资源上限

  • 控制池大小、防止耗尽:
    • pool_size: 5–20(按实例核数与并发),max_overflow: 0–10,pool_timeout: 30s,pool_recycle: 1800s,pool_pre_ping: True。
  • 禁止无限增长:max_overflow 设定上限;遭遇慢查询时避免堆积导致服务不可用。
  • 健康检查:pre_ping 避免死连接;对数据库断线设置重试策略(有限次数+指数退避),不要无休止重试。

五、输入验证、输出编码与转义

  • 输入:Pydantic 模型验证类型、长度、范围与枚举白名单;对自由文本设上限长度并规范字符集。
  • 输出:
    • FastAPI JSONResponse 默认安全编码;不要把原始 SQL 或异常堆栈输出给客户端。
    • 如使用 Jinja2 模板,开启 autoescape;绝不关闭全局转义。
    • CSV/TSV 导出使用标准库 csv,确保正确引用与分隔;设置 Content-Type 与编码。
  • 日志与监控:
    • 结构化日志,避免记录敏感字段(密码、令牌、密钥、完整 SQL)。对可能包含用户输入的日志进行转义或清洗。
    • 禁用 SQLAlchemy echo,生产环境关闭 debug。

六、防盲注与时间差侧信道

  • 核心防护:参数化查询与白名单可消除多数注入向量,包括盲注。
  • 附加措施:
    • 统一错误消息与状态码,避免因错误类型差异暴露表/列信息或布尔盲注信号。
    • 严格语句/请求超时,阻断时间型盲注(如 sleep())造成的可观测延迟。
    • 速率限制与WAF/反爬:限制枚举与探测频度,降低统计盲注的样本获取速度。
    • 结果一致性:避免“存在/不存在”返回时间明显差异;对不存在资源同样走索引查询与标准化响应路径。

七、统一错误处理,不泄露结构信息

  • FastAPI 全局异常处理器:捕获 SQLAlchemyError、IntegrityError、DBAPIError,返回通用错误消息与 5xx/4xx;内部日志记录详细信息与请求ID,但不返回给客户端。
  • 示例:
    • app.add_exception_handler(SQLAlchemyError, handle_db_errors)
    • 在 handler 中返回 {"error":"Database operation failed"},附带 correlation_id。
  • 屏蔽表/列名、约束名:不要把违反唯一约束、外键约束的原始错误透传;转换为业务级错误码。

八、审计追踪(可证明性与取证)

  • 审计事件内容:actor_id、动作类型(create/update/delete/read)、对象类型与ID、变更摘要(字段列表,不记录敏感值明文)、结果、来源IP、UA、请求ID、时间戳。
  • 写操作强制记录审计事件(事务内或独立可靠队列):
    • 可在同一事务中插入审计表,或发布到消息队列后异步落盘。
    • 对读取操作可抽样或按敏感表强制记录。
  • 数据库触发器辅助:对关键表建立 INSERT/UPDATE/DELETE 触发器写入审计表(注意性能与循环依赖);应用层仍需补充 actor 上下文。
  • 不在审计/日志中记录密文或令牌;如需对比变更,存储哈希或字段名集合。

九、凭证与密钥管理(环境/密管)

  • 开发与生产分离:开发可用 .env(仅本地,不提交版本库);生产使用环境变量或密钥管理服务(AWS Secrets Manager、GCP Secret Manager、Azure Key Vault、HashiCorp Vault、K8s Secrets)。
  • 最佳实践:
    • 应用启动时检索凭证,不在日志打印 DSN。
    • 定期轮换;为不同服务/组件使用不同凭证;限制来源 IP/网络。
    • 使用云原生身份(IAM/Workload Identity)访问密管,避免在应用中嵌入密钥。
  • 代码示例(pydantic-settings + env/密管):
    • class Settings: DATABASE_URL: SecretStr = os.getenv("DATABASE_URL")
    • 从密管取值后组合 DSN;对 SecretStr 调用 get_secret_value 时仅在内存使用,不记录。

十、端到端示例(FastAPI 片段)

  • 会话与事务依赖:
    • @asynccontextmanager async def tx_session(read_only=False, timeout_ms=2000): async with AsyncSessionFactory() as session: async with session.begin(): await session.execute(text("SET LOCAL statement_timeout=:t").bindparams(bindparam("t")), {"t": timeout_ms}) if read_only: await session.execute(text("SET TRANSACTION READ ONLY")) yield session
  • 路由:
    • @app.get("/items") async def list_items(params: ListParams, session: AsyncSession = Depends(lambda: tx_session(read_only=True))):

      验证 + 白名单 + 参数化查询,如前述

      return items
    • @app.post("/items") async def create_item(payload: CreateItem, session: AsyncSession = Depends(tx_session)):

      写操作 + 审计记录

      await session.add(item) await session.flush() await session.execute(insert(AuditEvent).values(...))
  • 统一错误处理与日志:
    • @app.exception_handler(SQLAlchemyError) async def db_error_handler(request, exc): log.error("db_error", err=str(exc.class), req_id=request.headers.get("X-Request-ID")); return JSONResponse({"error":"Database operation failed"}, status_code=500)

十一、其他加固建议

  • 输入限流与分页上限,防数据枚举与DoS。
  • 对批量写/删操作设置单次数量上限与服务器端校验。
  • 针对导出/报表使用只读角色与只读事务,并设置语句超时与行数上限。
  • 安全测试:在 CI 中加入 SAST(Bandit)、依赖漏洞扫描(pip-audit)、动态测试(针对 API 的注入/错误泄露用例);生产前进行渗透测试与性能基准。

遵循以上设计与实现建议,可在 FastAPI 应用的数据库读写与查询构建中有效防止注入与信息泄露,满足安全编码与应用安全规范。

示例详情

该提示词已被收录:
“程序员必备:提升开发效率的专业AI提示词合集”
让 AI 成为你的第二双手,从代码生成到测试文档全部搞定,节省 80% 开发时间
√ 立即可用 · 零学习成本
√ 参数化批量生成
√ 专业提示词工程师打磨

📖 如何使用

30秒出活:复制 → 粘贴 → 搞定
与其花几十分钟和AI聊天、试错,不如直接复制这些经过千人验证的模板,修改几个 {{变量}} 就能立刻获得专业级输出。省下来的时间,足够你轻松享受两杯咖啡!
加载中...
💬 不会填参数?让 AI 反过来问你
不确定变量该填什么?一键转为对话模式,AI 会像资深顾问一样逐步引导你,问几个问题就能自动生成完美匹配你需求的定制结果。零门槛,开口就行。
转为对话模式
🚀 告别复制粘贴,Chat 里直接调用
无需切换,输入 / 唤醒 8000+ 专家级提示词。 插件将全站提示词库深度集成于 Chat 输入框。基于当前对话语境,系统智能推荐最契合的 Prompt 并自动完成参数化,让海量资源触手可及,从此彻底告别"手动搬运"。
即将推出
🔌 接口一调,提示词自己会进化
手动跑一次还行,跑一百次呢?通过 API 接口动态注入变量,接入批量评价引擎,让程序自动迭代出更高质量的提示词方案。Prompt 会自己进化,你只管收结果。
发布 API
🤖 一键变成你的专属 Agent 应用
不想每次都配参数?把这条提示词直接发布成独立 Agent,内嵌图片生成、参数优化等工具,分享链接就能用。给团队或客户一个"开箱即用"的完整方案。
创建 Agent

✅ 特性总结

一键生成针对不同语言或框架的安全编码指导,高效解决开发过程中的安全隐患。
轻松获取适用于敏感操作场景的防护建议,全面覆盖设计与实现阶段的安全风险。
自动输出安全编码最佳实践,帮助开发者降低安全漏洞发生的概率。
提供智能化建议,确保应用符合行业安全规范和审计要求。
支持灵活调参,根据特定需求定制个性化安全解决方案,满足多样化应用场景。
针对特定操作风险识别,提供针对性的预防措施,助力提高代码安全性。
快速优化开发安全标准,减少不必要的时间成本及资源投入。
强化开发者的安全意识与能力,让安全编码变得更加高效和可落地。
覆盖多种高风险操作场景,助力团队构建稳健可靠的技术架构。

🎯 解决的问题

为开发人员提供敏感操作的安全编码最佳实践,帮助他们确保应用在设计与实现阶段遵循安全规范,降低潜在安全风险。

🕒 版本历史

当前版本
v2.1 2024-01-15
优化输出结构,增强情节连贯性
  • ✨ 新增章节节奏控制参数
  • 🔧 优化人物关系描述逻辑
  • 📝 改进主题深化引导语
  • 🎯 增强情节转折点设计
v2.0 2023-12-20
重构提示词架构,提升生成质量
  • 🚀 全新的提示词结构设计
  • 📊 增加输出格式化选项
  • 💡 优化角色塑造引导
v1.5 2023-11-10
修复已知问题,提升稳定性
  • 🐛 修复长文本处理bug
  • ⚡ 提升响应速度
v1.0 2023-10-01
首次发布
  • 🎉 初始版本上线
COMING SOON
版本历史追踪,即将启航
记录每一次提示词的进化与升级,敬请期待。

💬 用户评价

4.8
⭐⭐⭐⭐⭐
基于 28 条评价
5星
85%
4星
12%
3星
3%
👤
电商运营 - 张先生
⭐⭐⭐⭐⭐ 2025-01-15
双十一用这个提示词生成了20多张海报,效果非常好!点击率提升了35%,节省了大量设计时间。参数调整很灵活,能快速适配不同节日。
效果好 节省时间
👤
品牌设计师 - 李女士
⭐⭐⭐⭐⭐ 2025-01-10
作为设计师,这个提示词帮我快速生成创意方向,大大提升了工作效率。生成的海报氛围感很强,稍作调整就能直接使用。
创意好 专业
COMING SOON
用户评价与反馈系统,即将上线
倾听真实反馈,在这里留下您的使用心得,敬请期待。
加载中...