环境变量与安全配置:从 .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")

这样一来:

  1. 敏感信息完全不在代码库
  2. 本地开发、测试、生产环境可以独立配置不同的密钥
  3. 符合《The Twelve-Factor App》(现代云原生应用的黄金标准)的「配置分离」原则

2. 工具准备:安装 python-dotenv

直接用pip安装就行,它是一个轻量级库,不需要额外依赖:

pip install python-dotenv

或者如果你用的是requirements.txt管理依赖,记得同步加上:

python-dotenv>=1.0.0

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

这些服务的好处是:

  1. 密钥加密存储,不会泄露到日志里
  2. 可以定期轮换密钥,不用重启应用
  3. 可以设置访问权限,只有指定的应用/服务器能读取

6. 安全避坑指南(必看!)

  1. 永远不要把.env文件提交到Git:哪怕是私库!私库也可能被泄露,或者你把私库转成公开库忘了删。
  2. 生产环境不要留fallback密钥:比如BaseConfig里的那个,生产环境必须用真实的、长的、随机的密钥。
  3. 生成随机密钥的小技巧:用Python自带的secrets模块生成,不要手动输入:
    import secrets
    print(secrets.token_hex(16))  # 生成32位的十六进制随机密钥(推荐用于SECRET_KEY)
  4. 不要把密钥打印到日志里:开发环境可以临时打印,生产环境必须关闭所有敏感信息的日志输出。

7. 小结

今天我们讲了Python项目通用的安全配置方案,核心流程很简单:

1. 安装:pip install python-dotenv
2. 本地配置:.env(秘密) + .env.example(模板) + .gitignore(黑名单)
3. 代码集成:load_dotenv() + os.getenv() + 多环境配置类
4. 生产部署:用系统/Docker/云平台变量替换本地.env

💡 黄金法则代码里只能放非敏感的配置(比如端口、默认缓存时间),所有敏感信息(密钥、密码、API Key、数据库连接字符串)都必须用环境变量隔离!


🔗 扩展阅读