Web 开发者安全最佳实践
Web 安全不是可选项。随着网络威胁日益增加,开发者必须在应用程序的每一层都构建安全措施。本指南涵盖了你今天就应该实施的基本安全实践。
密码安全
永远不要存储明文密码
始终使用现代算法(如 bcrypt、Argon2 或 scrypt)来哈希密码。这些算法被设计为运算缓慢,使暴力破解攻击变得不切实际。
// 推荐:使用 bcrypt
const bcrypt = require('bcrypt');
const hash = await bcrypt.hash(password, 12);
哈希算法对比
并非所有哈希算法都一样。选择合适的算法取决于你的威胁模型和使用场景:
| 算法 | 输出大小 | 速度 | 使用场景 | 安全状态 |
|---|---|---|---|---|
| MD5 | 128 位 | 非常快 | 校验和、非安全哈希 | 安全性已被破解 |
| SHA-256 | 256 位 | 快 | 数据完整性、数字签名 | 安全 |
| bcrypt | 184 位 | 慢(可调) | 密码哈希 | 安全 |
| Argon2 | 可配置 | 慢(可调) | 密码哈希(现代方案) | 新项目推荐使用 |
bcrypt 和 Argon2 被设计为故意低速——这是特性,不是缺陷。每次哈希操作需要数十到数百毫秒,使大规模暴力破解在经济上变得不可行。
理解密码熵
密码强度可以用熵进行数学量化:entropy = log2(charset_size^length)。使用小写字母(26 个字符)的 8 位密码约有 37.6 位熵。而混合大小写字母、数字和符号(95 个字符)的 16 位密码约有 105 位熵——破解难度呈指数级增长。这就是为什么对大多数用户来说,密码长度比复杂度更重要。想深入了解密码强度背后的数学原理,请阅读我们的密码熵详解。
使用密码管理器
建议你的用户使用密码管理器。人类选择的密码往往遵循可预测的模式,攻击者利用字典攻击加以利用。密码管理器生成真正的随机字符串,并消除跨服务的密码重用——这是凭证填充攻击最常见的攻击向量之一。
使用足够的盐值轮次
盐值轮次决定了计算成本。更高的值更安全但速度更慢。对于大多数应用来说,10-12 轮是一个良好的平衡点。
输入验证
在客户端和服务端都进行验证
客户端验证可以改善用户体验,但服务端验证对安全至关重要。永远不要信任客户端输入。
清理所有用户输入
通过清理输入来防止注入攻击:
- 使用参数化查询防止 SQL 注入
- 转义 HTML 输出以防止 XSS 攻击
- 严格验证文件上传
具体的攻击示例
理解真实的攻击有助于你进行防御。假设一个评论表单直接将用户输入渲染到 HTML 中。攻击者提交了:
<script>alert('xss')</script>
如果应用在渲染时没有转义,该脚本会在每个访问者的浏览器中执行——窃取 cookie、重定向用户或注入键盘记录器。解决方案:始终根据上下文对输出进行编码。使用 DOMPurify 等库进行 HTML 清理。
SQL 注入同样危险。在登录表单中,攻击者输入用户名:
' OR 1=1 --
如果查询是通过字符串拼接构建的("SELECT * FROM users WHERE username='" + input + "'"),这将完全绕过身份验证。-- 会注释掉查询的其余部分。解决方案:始终使用参数化查询(也称为预处理语句)。每个主流数据库库都支持:
// 错误:字符串拼接
db.query(`SELECT * FROM users WHERE username='${input}'`);
// 正确:参数化查询
db.query('SELECT * FROM users WHERE username = $1', [input]);
内容安全策略(CSP)
作为纵深防御策略,部署内容安全策略头部。CSP 告诉浏览器哪些内容来源是可信的,有效地阻止内联脚本和未授权的资源加载。即使你的代码中存在 XSS 漏洞,严格的 CSP 也能阻止注入脚本的执行。从 Content-Security-Policy: default-src 'self' 开始,根据需要逐步添加例外。
哈希函数
选择正确的哈希算法
不同的使用场景需要不同的哈希函数:
| 使用场景 | 推荐算法 |
|---|---|
| 密码 | bcrypt、Argon2 |
| 完整性验证 | SHA-256 |
| 校验和 | SHA-256、MD5(非安全场景) |
| 快速哈希 | BLAKE3 |
理解哈希输出与碰撞
MD5 生成 128 位(32 个十六进制字符)哈希值,而 SHA-256 生成 256 位(64 个十六进制字符)哈希值。这个差异很重要:更大的输出空间意味着指数级增长的哈希可能值,碰撞概率大幅降低。碰撞是指两个不同的输入产生相同的哈希值——能够制造碰撞的攻击者可以伪造数字签名或篡改已验证的数据。
MD5 碰撞在现代硬件上可以在几秒内生成。SHA-256 仍然具有抗碰撞性,目前没有已知的实际攻击。这就是为什么为正确的场景选择正确的算法至关重要:
- 校验和与去重:当安全性不是关注点时,MD5 是可以接受的
- 数据完整性与签名:SHA-256 提供强抗碰撞性
- 密码存储:bcrypt 或 Argon2,它们增加了盐值和故意的计算延迟
用于消息认证的 HMAC
当你需要同时验证消息的完整性和真实性时,使用 HMAC(基于哈希的消息认证码)。HMAC 将哈希函数与密钥结合,确保只有知道密钥的各方才能生成或验证标签。这对于 API 认证、Webhook 验证和安全令牌生成至关重要。
永远不要将 MD5 或 SHA-1 用于安全目的
MD5 和 SHA-1 在安全方面已经被破解。请使用 SHA-256 或 SHA-3 进行加密哈希。
全面使用 HTTPS
TLS 的实际作用
TLS(传输层安全协议)提供三个关键保护:传输加密(防止窃听)、服务器认证(证明你正在与真实的服务器通信,而非冒充者)以及数据完整性(检测传输中的任何篡改)。没有 TLS,用户和服务器之间的所有数据——密码、令牌、个人信息——都以明文形式传输。
始终使用 TLS
- 从受信任的 CA 获取证书(Let’s Encrypt 是免费且完全自动化的)
- 将 HTTP 重定向到 HTTPS
- 使用 HSTS 头部
- 保持 TLS 版本更新
HSTS 与混合内容
HTTP 严格传输安全(HSTS)头部告诉浏览器只能通过 HTTPS 连接,即使用户输入了 http://。设置 Strict-Transport-Security: max-age=31536000; includeSubDomains 可以在所有子域上强制执行一整年。这能防止 SSL 剥离攻击,即攻击者将连接降级为 HTTP。
注意混合内容警告:如果你的 HTTPS 页面通过 HTTP 加载图片、脚本或样式表,浏览器会阻止或警告。审查你的页面中硬编码的 http:// URL,使用协议相对路径或对所有资源强制使用 HTTPS。
身份认证
实施速率限制
通过速率限制防止暴力破解攻击:
- 限制每个 IP 的登录尝试次数
- 在失败尝试后添加延迟
- 对可疑活动使用验证码
JWT 认证基础
JSON Web Token(JWT)提供了一种无状态的认证机制,结构为 header.payload.signature。服务器使用密钥签署令牌,客户端在后续请求中携带它。由于令牌包含用户的声明信息,服务器不需要在每次请求时查找会话状态——这使得 JWT 非常适合分布式系统和微服务架构。
始终为访问令牌设置较短的过期时间(例如 15 分钟),并使用刷新令牌来获取新的访问令牌。安全地存储刷新令牌(httpOnly cookie,而非 localStorage),并实施令牌轮换,使每个刷新令牌只能使用一次。
多因素认证(MFA)
对于任何严肃的应用程序,MFA 不再是可选项。要求第二因素——TOTP 代码(Google Authenticator)、硬件密钥(YubiKey)或推送通知——能够显著降低密码泄露的影响。即使攻击者获得了有效的凭证,没有第二因素也无法完成认证。
会话固定攻击防护
会话固定攻击发生在攻击者在用户认证之前设置一个已知的会话 ID。登录后,攻击者使用该会话 ID 劫持已认证的会话。防止此攻击的方法是在认证成功后始终重新生成会话 ID并使旧 ID 失效。
使用安全的会话管理
- 生成加密随机的会话 ID
- 在 cookie 上设置 secure 和 httpOnly 标志
- 实施会话超时
- 在登出时使会话失效
安全头部清单
部署正确的 HTTP 响应头是加固应用程序最有效且最低成本的方式之一。以下是基本安全头部的快速参考表:
| 头部 | 用途 | 示例值 |
|---|---|---|
| Content-Security-Policy | 防止 XSS 和数据注入 | default-src 'self' |
| Strict-Transport-Security | 强制 HTTPS 连接 | max-age=31536000; includeSubDomains |
| X-Content-Type-Options | 防止 MIME 类型嗅探 | nosniff |
| X-Frame-Options | 防止点击劫持 | DENY |
| Referrer-Policy | 控制引用来源信息 | strict-origin-when-cross-origin |
这些头部可以在 Web 服务器层(Nginx、Apache)、CDN/边缘层(Cloudflare、Vercel)或应用框架中设置。使用 securityheaders.com 等工具测试你的头部配置。目标是达到 A+ 评级——大多数头部只需一行配置,部署零成本。
使用我们的安全工具
探索我们的安全工具来帮助你的开发工作:
如需更全面地了解编码、哈希和转换工具如何融入你的开发工作流程,请参阅我们的开发者工具必备指南。
常见问题
最常见的 Web 安全漏洞是什么?
跨站脚本攻击(XSS)是 OWASP 报告中最普遍的 Web 安全漏洞。当应用程序在未经适当验证的情况下将不可信数据嵌入网页时,就会发生 XSS。通过对所有用户输入进行清理、使用内容安全策略(CSP)头部,以及根据上下文(HTML、JavaScript、URL 或 CSS)对输出进行编码来防止 XSS。
MD5 还能用于密码哈希吗?
不能 — MD5 绝不应用于密码哈希。MD5 计算速度极快,这使其容易受到暴力破解和彩虹表攻击。现代 GPU 每秒可计算数十亿次 MD5 哈希。请改用 bcrypt、scrypt 或 Argon2,这些算法被设计为故意低速,并内置加盐机制以抵御攻击。
2026 年安全密码应该有多长?
建议至少 12 个字符,但 16 个字符以上能提供显著更强的保护。长度比复杂度更重要 — 一个 20 字符的密码短语(如 “correct-horse-battery-staple”)比一个简短的复杂密码(如 “P@ss1!”)更安全。对于关键账户,无论密码长度如何,都应启用多因素认证(MFA)。
加密和哈希有什么区别?
加密是可逆的 — 你可以使用密钥将数据解密还原为原始形式。哈希是单向的 — 你无法从哈希值恢复原始数据。对需要检索的数据(如存储的用户数据)使用加密,对只需验证的数据(如密码和校验和)使用哈希。
我应该自己实现认证系统吗?
不建议 — 从零构建认证系统风险大且容易出错。请使用经过实战验证的框架和服务,如 Auth0、Firebase Auth 或 Supabase Auth。它们处理密码哈希、会话管理、令牌轮换、多因素认证和暴力破解防护。将开发时间集中在应用程序的独特功能上。
总结
安全是一个持续的过程,而不是一次性的任务。及时了解最新的漏洞信息,定期审计你的代码,并遵循最小权限原则。用户将数据托付给你 - 请用强大的安全实践来回报这份信任。