是kimi主笔的~ 学自家的项目学到了api key注入的正确方式,觉得很有用啊,于是让kimi写了存档~
API Key 安全注入范式:从「写死代码」到「零信任部署」
基于 GitHub Actions → 云服务器 的实战经验,提炼一套通用、可复现的 API Key 全生命周期管理方案。
一、核心原则:三条不可妥协的底线
| 原则 |
含义 |
反面教材 |
| 不进仓库 |
任何密钥不得出现在 Git 历史、配置文件或构建产物中 |
把 api.key=sk-xxx 提交到 application.properties |
| 不进构建产物 |
密钥不应被打包进 jar/docker image,避免镜像泄露即密钥泄露 |
在 Dockerfile 里 ENV API_KEY=xxx |
| 只在运行时注入 |
密钥仅在进程启动时通过环境变量/挂载文件进入内存 |
把密钥写在 systemd 的 ExecStart 命令行参数里 |
二、通用架构:四层漏斗模型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| ┌─────────────────────────────────────────┐ │ Layer 1: 密钥托管平台 │ ← GitHub Secrets / AWS Secrets Manager / 1Password │ (COZE_API_TOKEN / OPENAI_API_KEY ...) │ └─────────────────┬───────────────────────┘ │ CI/CD 流水线注入 ▼ ┌─────────────────────────────────────────┐ │ Layer 2: 服务器密钥文件 │ ← /opt/app/config/api.env (600权限) │ (仅 root + 服务用户可读) │ └─────────────────┬───────────────────────┘ │ systemd / docker-compose 加载 ▼ ┌─────────────────────────────────────────┐ │ Layer 3: 进程运行时环境变量 │ ← Spring Boot / Node / Python 读取 │ (内存中,不持久化) │ └─────────────────┬───────────────────────┘ │ 占位符解析 ▼ ┌─────────────────────────────────────────┐ │ Layer 4: 应用层配置 │ ← ${API_KEY:} 占位符,空值优雅降级 │ (代码中无明文,无默认值) │ └─────────────────────────────────────────┘
|
三、逐层落地:从代码到服务器的完整配置
Layer 4:代码层 —— 占位符 + 本地覆盖
配置文件(resources/api.properties):
1 2 3 4 5 6 7 8 9
| ai.provider.url=https://api.example.com/v1 ai.provider.model=gpt-4
ai.provider.key=${AI_API_KEY:}
ai.enabled=${AI_API_KEY:}
|
主配置导入(application.properties):
1
| spring.config.import=optional:classpath:api.properties,optional:classpath:api-local.properties
|
本地开发覆盖(api-local.properties,已加入 .gitignore):
1
| ai.provider.key=sk-local-test-key
|
.gitignore 关键行:
Layer 3:应用层 —— 优雅降级
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @Service public class AiService { @Value("${ai.provider.key:}") private String apiKey; @Value("${ai.provider.url}") private String apiUrl; public boolean isEnabled() { return StringUtils.hasText(apiKey); } public AnalysisResult analyze(String code) { if (!isEnabled()) { return AnalysisResult.disabled("AI 分析未启用"); } } }
|
关键设计:无密钥时服务不崩溃,前端显示「AI 未配置」即可。
Layer 2:服务器层 —— 文件权限最小化
创建密钥文件:
1 2 3 4 5 6 7 8
| sudo mkdir -p /opt/myapp/config sudo tee /opt/myapp/config/api.env << 'EOF' AI_API_KEY=sk-prod-xxxxxxxx EOF
sudo chmod 600 /opt/myapp/config/api.env sudo chown root:root /opt/myapp/config/api.env
|
为什么用文件而非直接 export?
- 环境变量在
ps e 或 /proc/$pid/environ 中可被其他用户读取
- 文件配合 systemd 的
EnvironmentFile 加载,权限控制更细
Layer 1:CI/CD 层 —— GitHub Actions 注入
GitHub Secrets 设置:
1 2 3
| Settings → Secrets and variables → Actions → New repository secret Name: AI_API_KEY Value: sk-prod-xxxxxxxx
|
deploy.yml 关键片段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| - name: Deploy & Inject Secrets uses: appleboy/ssh-action@master with: host: ${{ secrets.SSH_HOST }} username: ${{ secrets.SSH_USER }} key: ${{ secrets.SSH_PRIVATE_KEY }} script: | # 写入密钥(注意:这里用 HERE DOCUMENT 避免 shell 历史记录) sudo mkdir -p /opt/myapp/config sudo tee /opt/myapp/config/api.env << 'EOF' AI_API_KEY=${{ secrets.AI_API_KEY }} EOF sudo chmod 600 /opt/myapp/config/api.env sudo systemctl daemon-reload sudo systemctl restart myapp
|
安全细节:
- 使用
<< 'EOF'(单引号)防止变量提前展开
- GitHub Actions 日志会自动 mask
secrets.* 的值,即使 set -x 也不会泄露
Layer 0:systemd 服务配置
/etc/systemd/system/myapp.service:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| [Unit] Description=MyApp Backend After=network.target
[Service] Type=simple User=appuser Group=appgroup
EnvironmentFile=/opt/myapp/config/api.env
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/java -jar /opt/myapp/myapp.jar
NoNewPrivileges=true ProtectSystem=strict ProtectHome=true
[Install] WantedBy=multi-user.target
|
应用:
1 2
| sudo systemctl daemon-reload sudo systemctl restart myapp
|
五、扩展场景
Docker / Docker Compose
1 2 3 4 5 6 7
| services: app: image: myapp:latest env_file: - /opt/myapp/config/api.env
|
多环境管理
| 环境 |
密钥来源 |
文件路径 |
| 本地 |
api-local.properties / export |
resources/ |
| 测试 |
GitHub Secrets (AI_API_KEY_STAGING) |
/opt/myapp-staging/config/api.env |
| 生产 |
GitHub Secrets (AI_API_KEY_PROD) |
/opt/myapp/config/api.env |