环境变量与安全配置:从 .env 加载配置,彻底隐藏密钥
📂 所属阶段:第六阶段 — 上线部署(生产篇)
🔗 相关章节:环境搭建 · 密码安全加密
0. 一个真实的“差点翻车”场景
上个月帮新人复盘项目,发现他把阿里云OSS的AccessKey Secret直接写在了Django的settings.py文件里!万幸的是还没push到公开仓库——要是公开了,这个账号下的免费20G空间可能第二天就被恶意爬虫塞满,账单可能飙到几千块。
这就是硬编码敏感信息的致命问题:只要代码出现在Git历史、公开代码片段、或者第三方托管平台的临时备份里,密钥就相当于公开了。
那怎么解决?今天的主角——python-dotenv管理的环境变量,就是所有Python项目通用的、低成本的安全配置方案。
1. 核心逻辑:为什么选环境变量?
我们先对比两种配置敏感信息的方式,一眼就能看出区别:
❌ 错误示范:硬编码到代码中
# app/config.py(绝对不要这么写!!)
SECRET_KEY = "dev-12345678abcdefgh"
AWS_ACCESS_KEY_ID = "AKIA2V3X7Y8ZABCDEF"
AWS_SECRET_ACCESS_KEY = "rT6uI9oP0qW2eR4tY5uI6oP7aS8dF9gH0jK1l"
哪怕本地加了.gitignore?万一哪天不小心删了、或者给同事打包代码忘了排除、或者用了历史提交查看工具,风险都是100%。
✅ 正确方案:用环境变量隔离
# app/config.py(安全!)
import os
SECRET_KEY = os.getenv("SECRET_KEY")
AWS_ACCESS_KEY_ID = os.getenv("AWS_ACCESS_KEY_ID")
AWS_SECRET_ACCESS_KEY = os.getenv("AWS_SECRET_ACCESS_KEY")
这样一来:
- 敏感信息完全不在代码库里
- 本地开发、测试、生产环境可以独立配置不同的密钥
- 符合《The Twelve-Factor App》(现代云原生应用的黄金标准)的「配置分离」原则
2. 工具准备:安装 python-dotenv
直接用pip安装就行,它是一个轻量级库,不需要额外依赖:
pip install python-dotenv
或者如果你用的是requirements.txt管理依赖,记得同步加上:
3. 本地开发:配置文件三件套
为了本地、团队协作、生产环境都顺畅,我们需要准备三个核心配置文件,各司其职:
3.1 .env 文件(核心敏感配置,本地独享)
这个文件是「本地秘密仓库」,所有密钥、本地测试用的数据库地址都放在这里,必须永远不上传Git!
# .env(绝对、绝对要加入.gitignore!)
FLASK_APP=app/__init__.py
FLASK_ENV=development
SECRET_KEY=dev-only-very-long-random-secret-key-change-in-production
DATABASE_URL=sqlite:///daoman_dev.db
JWT_SECRET=dev-jwt-super-long-secret-key-change-now
MAIL_SERVER=smtp.qq.com
MAIL_PORT=587
MAIL_USERNAME=your_qq_email@qq.com
MAIL_PASSWORD=your_qq_authorization_code
3.2 .env.example 文件(团队协作模板,必须上传)
这个文件是「给队友和未来的自己看的说明书」,只放配置项的格式和占位符,不能有任何真实的密钥:
# .env.example(上传Git!复制一份改名为.env就能用)
FLASK_APP=app/__init__.py
FLASK_ENV=development
SECRET_KEY=change-this-to-a-32-character-random-secret
DATABASE_URL=sqlite:///daoman_dev.db
JWT_SECRET=change-this-to-a-32-character-random-jwt-secret
MAIL_SERVER=smtp.example.com
MAIL_PORT=587
MAIL_USERNAME=your@email.com
MAIL_PASSWORD=your-email-password-or-authorization-code
3.3 .gitignore 文件(Git的“黑名单”,必须配置)
这个文件告诉Git「哪些文件不要跟踪」,一定要把.env开头的敏感文件放进去:
# .gitignore(Git标准配置,顺便加上Python项目常用的)
.env
.env.local
.env.production
*.db
*.sqlite3
__pycache__/
*.pyc
*.pyo
*.pyd
venv/
.venv/
.coverage
htmlcov/
static/uploads/
*.log
4. 代码集成:加载并使用环境变量
接下来就是把这些配置集成到项目里,不管是Flask、Django、FastAPI还是纯Python脚本,逻辑都差不多。
以Flask项目为例(config.py)
我们可以把配置分成「基础配置」「开发配置」「测试配置」「生产配置」四个类,逻辑更清晰:
# app/config.py
import os
from dotenv import load_dotenv
# 第一步:加载.env文件(只在本地开发/测试环境加载)
# 如果是生产环境,直接用系统/云平台的环境变量,不需要这个文件
load_dotenv()
class BaseConfig:
"""所有环境通用的基础配置"""
SECRET_KEY = os.getenv("SECRET_KEY", "unsafe-fallback-secret-only-for-testing")
SQLALCHEMY_DATABASE_URI = os.getenv("DATABASE_URL", "sqlite:///app_default.db")
SQLALCHEMY_TRACK_MODIFICATIONS = False # 关闭这个功能可以节省内存
# Session安全配置(生产环境必须开启!)
SESSION_COOKIE_SECURE = os.getenv("FLASK_ENV") == "production" # 生产环境必须用HTTPS
SESSION_COOKIE_HTTPONLY = True # 禁止JavaScript访问Cookie,防止XSS攻击
SESSION_COOKIE_SAMESITE = "Lax" # 防止CSRF攻击
# 邮件配置(如果项目不用邮件,可以去掉)
MAIL_SERVER = os.getenv("MAIL_SERVER")
MAIL_PORT = int(os.getenv("MAIL_PORT", 587))
MAIL_USE_TLS = os.getenv("MAIL_USE_TLS", "True").lower() == "true"
MAIL_USERNAME = os.getenv("MAIL_USERNAME")
MAIL_PASSWORD = os.getenv("MAIL_PASSWORD")
class DevelopmentConfig(BaseConfig):
"""本地开发环境专用配置"""
DEBUG = True
TESTING = False
class TestingConfig(BaseConfig):
"""单元测试/集成测试专用配置"""
DEBUG = True
TESTING = True
SQLALCHEMY_DATABASE_URI = "sqlite:///:memory:" # 内存数据库,测试完自动清空
WTF_CSRF_ENABLED = False # 测试时禁用CSRF,简化测试流程
class ProductionConfig(BaseConfig):
"""生产环境专用配置"""
DEBUG = False
TESTING = False
# 生产环境必须配置真实的SECRET_KEY和DATABASE_URL,否则直接报错
@classmethod
def init_app(cls, app):
super().init_app(app)
if app.config["SECRET_KEY"] == "unsafe-fallback-secret-only-for-testing":
raise ValueError("生产环境必须设置真实的SECRET_KEY!请检查系统环境变量。")
if not app.config["MAIL_PASSWORD"] and app.config.get("MAIL_USE_TLS"):
raise ValueError("生产环境邮件服务需要配置真实的密码!")
# 配置映射字典,方便根据环境切换
config = {
"development": DevelopmentConfig,
"testing": TestingConfig,
"production": ProductionConfig,
"default": DevelopmentConfig
}
在Flask启动文件中引入(init.py)
# app/__init__.py
from flask import Flask
from app.config import config
def create_app(config_name="default"):
app = Flask(__name__)
# 加载对应环境的配置
app.config.from_object(config[config_name])
# 这里可以继续初始化数据库、蓝图等
# ...
return app
5. 生产环境:替换本地.env,用系统/云平台变量
生产环境绝对不能依赖本地的.env文件——要么服务器登录后手动设置,要么用Docker/K8s的环境变量配置,要么用云平台的密钥管理服务(更安全)。
方式1:临时设置(Shell命令,只对当前终端有效)
# Linux/macOS
export FLASK_ENV=production
export SECRET_KEY="prod-32-character-random-secret-key-abcd1234"
export DATABASE_URL="postgresql://user:password@db-server:5432/myapp_prod"
# Windows PowerShell
$env:FLASK_ENV="production"
$env:SECRET_KEY="prod-32-character-random-secret-key-abcd1234"
方式2:Docker容器设置
# Dockerfile(可以在构建时设置默认值,但生产环境要覆盖)
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# 设置默认配置项
ENV FLASK_APP=app/__init__.py
ENV FLASK_ENV=production
# 启动命令
CMD ["gunicorn", "app:create_app('production')", "--bind", "0.0.0.0:8000"]
启动容器时覆盖敏感变量:
docker run -d \
-p 8000:8000 \
-e SECRET_KEY="prod-32-character-random-secret-key-abcd1234" \
-e DATABASE_URL="postgresql://user:password@db-server:5432/myapp_prod" \
my-flask-app:latest
方式3:云平台密钥管理(推荐生产环境用)
如果预算允许或者有合规要求,建议用云平台的密钥管理服务:
- 阿里云:KMS密钥管理服务
- 腾讯云:云API密钥+KMS
- AWS:Secrets Manager
- Heroku:Config Vars
- Vercel:Environment Variables
这些服务的好处是:
- 密钥加密存储,不会泄露到日志里
- 可以定期轮换密钥,不用重启应用
- 可以设置访问权限,只有指定的应用/服务器能读取
6. 安全避坑指南(必看!)
- 永远不要把.env文件提交到Git:哪怕是私库!私库也可能被泄露,或者你把私库转成公开库忘了删。
- 生产环境不要留fallback密钥:比如BaseConfig里的那个,生产环境必须用真实的、长的、随机的密钥。
- 生成随机密钥的小技巧:用Python自带的secrets模块生成,不要手动输入:
import secrets
print(secrets.token_hex(16)) # 生成32位的十六进制随机密钥(推荐用于SECRET_KEY)
- 不要把密钥打印到日志里:开发环境可以临时打印,生产环境必须关闭所有敏感信息的日志输出。
7. 小结
今天我们讲了Python项目通用的安全配置方案,核心流程很简单:
1. 安装:pip install python-dotenv
2. 本地配置:.env(秘密) + .env.example(模板) + .gitignore(黑名单)
3. 代码集成:load_dotenv() + os.getenv() + 多环境配置类
4. 生产部署:用系统/Docker/云平台变量替换本地.env
💡 黄金法则:代码里只能放非敏感的配置(比如端口、默认缓存时间),所有敏感信息(密钥、密码、API Key、数据库连接字符串)都必须用环境变量隔离!
🔗 扩展阅读