应使用环境变量而非硬编码连接字符串,通过os.getenv()读取并校验非空,URI需URL编码、带合法协议头,本地用mongodb://、云服务用mongodb+srv://,且须白名单校验netloc防止恶意地址。

连接字符串里藏了密码,硬编码会出事
直接把 MONGODB_URI 写死在代码里,等于把数据库钥匙贴在源码上。Git 提交、日志打印、错误堆栈都可能泄露它。哪怕只是本地开发,也别图省事写 "mongodb://user:pass@localhost:27017/db" —— 这种字符串一旦进版本库,就得当安全事故处理。
正确做法是:从环境变量读取,且确保该变量不被意外打印或记录。
- 用
os.getenv("MONGODB_URI")读取,而不是os.environ["MONGODB_URI"](后者会抛KeyError,容易暴露配置缺失) - 启动时校验非空:
if not uri: raise ValueError("MONGODB_URI is missing") - 避免在日志中输出完整 URI —— 尤其不能用
logger.info(f"Connecting to {uri}"),可只打掩码后的前缀:logger.info(f"Connecting to {uri.split('@')[-1]}")
PyMongo 4.x 要求 URI 必须带 scheme 和 host,否则报错
mongodb:// 开头不是可选的,PyMongo 4.0+ 会严格校验。常见错误是漏掉协议头,比如写成 "localhost:27017" 或 "user:pass@localhost:27017",运行时直接抛 InvalidURI: Invalid URI scheme: expected 'mongodb' or 'mongodb+srv'。
另外,mongodb+srv:// 协议必须搭配 DNS SRV 记录,本地测试别误用 —— 它会尝试解析 _mongodb._tcp.example.com 这类记录,超时后才 fallback,拖慢启动。
立即学习“Python免费学习笔记(深入)”;
- 本地开发用
mongodb://,线上托管服务(如 Atlas)才用mongodb+srv:// - URI 中用户名密码需 URL 编码,特别是含
@、/、:的密码 —— 用urllib.parse.quote_plus()处理,别手写 - 连接池参数建议显式指定:
?maxPoolSize=10&minPoolSize=2&connectTimeoutMS=5000,避免默认值在高并发下成为瓶颈
敏感字段不能靠 .env 文件“假装安全”
.env 文件本身不加密,也不受 Git 忽略保护 —— 很多人忘了加 .gitignore,或者误把 .env 提交到公开仓库。更糟的是,有些框架(如 Flask-SQLAlchemy 示例)会自动加载 .env,连带把 MONGODB_URI 一起注入,根本没做权限隔离。
真正安全的做法是:让运维通过容器环境变量或密钥管理服务(如 AWS Secrets Manager、HashiCorp Vault)注入,代码只负责读取,不参与存储或传输。
- 开发时用
python-dotenv可以,但必须确认.env在.gitignore里,且文件权限为600(chmod 600 .env) - CI/CD 流水线中禁用
.env,改用平台提供的 secret 注入机制 - 不要用
configparser或 JSON 配置文件存 URI —— 这些格式同样不加密,且更容易被脚本批量提取
连接前先做基础解析和白名单校验
光靠 PyMongo 的 URI 校验不够。攻击者可能构造恶意 URI,比如指向内网地址(mongodb://192.168.1.100:27017)或加入危险选项(&authMechanism=MONGODB-X509 导致证书路径遍历)。简单过滤比事后补救更有效。
建议在 connect() 前加一层轻量检查:
- 用
urllib.parse.urlparse()提取netloc,检查是否在允许域名/IP 白名单内(如只允许cluster0.mongodb.net或localhost) - 拒绝包含
localhost以外的私有 IP 段(10.0.0.0/8、172.16.0.0/12、192.168.0.0/16) - 禁止 URI 中出现
unix_socket、ssl_ca_certs等可能触发本地文件读取的参数(除非明确需要)
真正难的不是怎么连 MongoDB,而是怎么确保你连的一定是那个你认为安全的 MongoDB —— URI 是入口,也是第一道防线,它被绕过一次,后面所有加密、认证都形同虚设。


















