错误安全
规则
- 在生产环境中,永远不要向客户端泄漏堆栈跟踪、数据库错误或文件路径
- 返回带有唯一错误ID的通用错误消息:
{ error: "出了点问题", errorId: "abc123" }
- 将完整的错误详细信息记录在服务器端,并使用相同的关联ID以便调试
- 使用不同的错误详细级别:开发环境(完整堆栈跟踪) vs 生产环境(安全消息 + ID)
- 使用全局错误处理器捕获所有未处理的错误——永远不要让原始错误到达客户端
- 在将第三方服务的错误消息转发给客户端之前,对其进行消毒
- 返回正确的HTTP状态码:400(错误输入)、401(未认证)、403(禁止访问)、404(未找到)、500(服务器错误)
import { randomUUID } from "node:crypto";
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
const errorId = randomUUID();
console.error({ errorId, message: err.message, stack: err.stack, url: req.url });
const statusCode = (err as any).statusCode || 500;
res.status(statusCode).json({
error: statusCode >= 500 ? "内部服务器错误" : err.message,
errorId,
...(process.env.NODE_ENV === "development" && { stack: err.stack }),
});
});
export default function GlobalError({ error, reset }) {
return (
<div>
<h2>出了点问题</h2>
<p>错误ID:{error.digest}</p>
<button onClick={reset}>重试</button>
</div>
);
}
避免
- 直接返回
err.message——数据库错误会泄漏表名、列名和查询结构
- 在生产响应中包含堆栈跟踪——它们会暴露文件路径、依赖项和内部架构
- 对所有错误使用通用的500——使用正确的状态码,以便客户端能够适当地处理错误
- 记录错误时不带关联ID——这会使得生产调试几乎不可能
- 静默地吞掉错误——始终记录,即使响应是通用的