硬编码密码到dsn中极危险,因git提交后密码永久留存、多环境无法安全隔离、ci/cd或容器中易泄露、二进制仍可被strings提取;应改用环境变量动态拼接dsn,并对日志中的dsn脱敏。

别把密码硬编码进 dsn 字符串里,这是最常见也最危险的错误。
为什么不能写死在代码里
硬编码账号密码(比如 "root:123456@tcp(localhost:3306)/test")会导致几个现实问题:
- Git 提交后密码直接暴露在历史记录中,哪怕删了也难彻底清理
- 不同环境(dev/staging/prod)共用同一份代码时,无法安全区分凭据
- CI/CD 流水线或容器部署时,密码可能被日志、进程列表或调试信息意外泄露
-
go build生成的二进制里字符串仍可被strings命令提取
推荐做法:用环境变量 + os.Getenv 拼接 dsn
把敏感字段抽离为环境变量,在运行时注入。GORM 本身不处理凭据管理,但你可以控制 dsn 的构建过程:
- 设置环境变量:
DB_USER=root、DB_PASSWORD=xxx、DB_HOST=localhost、DB_PORT=3306、DB_NAME=test - 代码中用
os.Getenv安全读取,缺失时 panic 或 fallback 到 error - 拼接
dsn时只用变量,不拼接明文常量
import (
"os"
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func initDB() (*gorm.DB, error) {
user := os.Getenv("DB_USER")
password := os.Getenv("DB_PASSWORD")
host := os.Getenv("DB_HOST")
port := os.Getenv("DB_PORT")
name := os.Getenv("DB_NAME")
if user == "" || password == "" || host == "" || port == "" || name == "" {
return nil, fmt.Errorf("missing required DB env vars")
}
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
user, password, host, port, name)
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
return db, err
}
更安全的进阶方案:用 .env 文件 + godotenv
开发阶段用 .env 文件管理凭据,配合 github.com/joho/godotenv 加载,但注意:
-
.env必须加进.gitignore,严禁提交 - 生产环境**不要依赖
.env**,应使用系统级环境变量或密钥管理服务(如 HashiCorp Vault、AWS Secrets Manager) -
godotenv.Load()要放在initDB之前,且建议只在非生产环境调用
容易忽略的关键点
很多人以为用了环境变量就万事大吉,其实还有几个隐蔽风险:
- 如果用
fmt.Printf或日志库打印了完整dsn,密码会直接出现在日志里——务必对dsn做脱敏处理(例如用正则替换密码段为"***") - MySQL 驱动默认开启
allowNativePasswords,旧版 MySQL 用户认证方式存在已知漏洞,建议升级到mysql_native_password或caching_sha2_password并确认服务端配置匹配 - 连接池参数(如
MaxOpenConns、MaxIdleConns)虽不涉密,但配置不当会导致连接耗尽或资源泄漏,间接影响凭据使用稳定性











