AES 字段加密迁配置并兼容旧密文
移除字段加密硬编码主密钥,改为 FIELD_ENCRYPTION_KEY 注入。 保留 FIELD_ENCRYPTION_LEGACY_KEY 透明回退旧密文,新写入统一使用主密钥;生产环境校验主密钥和 legacy 密钥长度、占位符及相等关系,并统一生产环境判断口径。 补充配置与旧密文兼容回归测试。
This commit is contained in:
@@ -34,6 +34,7 @@ import (
|
||||
"hfb_sys/backend/internal/modules/withdrawal"
|
||||
|
||||
_ "hfb_sys/backend/docs" // Swagger 文档
|
||||
"hfb_sys/backend/pkg/crypto"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
swaggerFiles "github.com/swaggo/files"
|
||||
@@ -42,7 +43,7 @@ import (
|
||||
)
|
||||
|
||||
func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine {
|
||||
if cfg.AppEnv == "production" {
|
||||
if config.IsProductionEnv(cfg.AppEnv) {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
|
||||
@@ -58,13 +59,53 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine {
|
||||
engine.GET("/health", health.Check)
|
||||
|
||||
// Swagger 文档路由(仅在非生产环境)
|
||||
if cfg.AppEnv != "production" {
|
||||
if !config.IsProductionEnv(cfg.AppEnv) {
|
||||
engine.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
||||
}
|
||||
if cfg.RateLimit.Enabled {
|
||||
engine.Use(middleware.RateLimitPerMinute(cfg.RateLimit.RequestsPerMinute, deps.Redis))
|
||||
}
|
||||
|
||||
// 业务字段加密器(实名/收款账号):主密钥 + legacy 回退,用于密钥轮换期间透明解出旧密文。
|
||||
// 生产环境必须配置 FIELD_ENCRYPTION_KEY(ValidateProductionSecurity 已校验)。
|
||||
// 开发/测试缺 FIELD_ENCRYPTION_KEY 时用 legacy 密钥构造单密钥加密器,
|
||||
// 保证零配置兼容历史硬编码密钥加密的存量密文(非生产 legacy 默认填历史硬编码值)。
|
||||
var fieldEncryptor crypto.Encryptor
|
||||
primary := cfg.FieldEncryptionKey
|
||||
legacy := cfg.FieldEncryptionLegacyKey
|
||||
switch {
|
||||
case primary != "":
|
||||
encryptor, err := crypto.NewFieldEncryptor(primary, legacy)
|
||||
if err != nil {
|
||||
if config.IsProductionEnv(cfg.AppEnv) {
|
||||
logger.Fatal("FIELD_ENCRYPTION_KEY invalid", zap.Error(err))
|
||||
}
|
||||
logger.Warn("FIELD_ENCRYPTION_KEY invalid, fallback to legacy-only", zap.Error(err))
|
||||
// primary 非法时退回 legacy 单密钥(若有),否则 MockEncryptor
|
||||
if legacy != "" {
|
||||
if e, eErr := crypto.NewFieldEncryptor(legacy); eErr == nil {
|
||||
fieldEncryptor = e
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fieldEncryptor = encryptor
|
||||
}
|
||||
case legacy != "":
|
||||
// 开发态零配置:用 legacy 单密钥,能解历史存量密文。
|
||||
encryptor, err := crypto.NewFieldEncryptor(legacy)
|
||||
if err == nil {
|
||||
fieldEncryptor = encryptor
|
||||
logger.Warn("FIELD_ENCRYPTION_KEY not set, using legacy-only encryptor for dev compatibility")
|
||||
}
|
||||
}
|
||||
if fieldEncryptor == nil {
|
||||
if config.IsProductionEnv(cfg.AppEnv) {
|
||||
logger.Fatal("FIELD_ENCRYPTION_KEY not set")
|
||||
}
|
||||
fieldEncryptor = &crypto.MockEncryptor{}
|
||||
logger.Warn("FIELD_ENCRYPTION_KEY and legacy both unset, using MockEncryptor")
|
||||
}
|
||||
|
||||
jwtManager := auth.NewJWTManager(cfg.JWTSecret)
|
||||
var userRepo *auth.UserRepository
|
||||
if deps.DB != nil {
|
||||
@@ -108,7 +149,7 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine {
|
||||
if deps.DB != nil {
|
||||
realnameRepo = realname.NewRepository(deps.DB)
|
||||
}
|
||||
realnameService := realname.NewService(realnameRepo, newRealnameProvider(cfg, logger), logger)
|
||||
realnameService := realname.NewService(realnameRepo, newRealnameProvider(cfg, fieldEncryptor, logger), logger)
|
||||
realnameHandler := realname.NewHandler(realnameService)
|
||||
var listingRepo *listing.Repository
|
||||
if deps.DB != nil {
|
||||
@@ -149,13 +190,13 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine {
|
||||
walletHandler := wallet.NewHandler(walletService)
|
||||
var paymentAccountRepo *paymentaccount.Repository
|
||||
if deps.DB != nil {
|
||||
paymentAccountRepo = paymentaccount.NewRepository(deps.DB)
|
||||
paymentAccountRepo = paymentaccount.NewRepository(deps.DB, fieldEncryptor)
|
||||
}
|
||||
paymentAccountService := paymentaccount.NewService(paymentAccountRepo)
|
||||
paymentAccountHandler := paymentaccount.NewHandler(paymentAccountService)
|
||||
var withdrawalRepo *withdrawal.Repository
|
||||
if deps.DB != nil {
|
||||
withdrawalRepo = withdrawal.NewRepository(deps.DB, walletRepo)
|
||||
withdrawalRepo = withdrawal.NewRepository(deps.DB, walletRepo, fieldEncryptor)
|
||||
}
|
||||
withdrawalService := withdrawal.NewService(withdrawalRepo)
|
||||
withdrawalHandler := withdrawal.NewHandler(withdrawalService)
|
||||
@@ -174,7 +215,7 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine {
|
||||
}
|
||||
}
|
||||
if encryptor == nil {
|
||||
if cfg.AppEnv == "production" {
|
||||
if config.IsProductionEnv(cfg.AppEnv) {
|
||||
logger.Fatal("PAYMENT_CONFIG_ENCRYPTION_KEY not set or invalid")
|
||||
}
|
||||
encryptor = &paymentconfig.MockEncryptor{}
|
||||
@@ -556,12 +597,13 @@ func newSMSProvider(cfg config.Config, logger *zap.Logger) smsintegration.Provid
|
||||
}
|
||||
}
|
||||
|
||||
func newRealnameProvider(cfg config.Config, logger *zap.Logger) realname.Provider {
|
||||
func newRealnameProvider(cfg config.Config, encryptor crypto.Encryptor, logger *zap.Logger) realname.Provider {
|
||||
switch strings.ToLower(strings.TrimSpace(cfg.Realname.Provider)) {
|
||||
case "cloudmarket", "aliyun_cloudmarket":
|
||||
provider, err := realname.NewCloudMarketProvider(realname.CloudMarketConfig{
|
||||
URL: cfg.Realname.CloudMarketURL,
|
||||
AppCode: cfg.Realname.CloudMarketAppCode,
|
||||
URL: cfg.Realname.CloudMarketURL,
|
||||
AppCode: cfg.Realname.CloudMarketAppCode,
|
||||
Encryptor: encryptor,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Warn("cloud market realname provider unavailable; realname verify will fail", zap.Error(err))
|
||||
@@ -569,6 +611,6 @@ func newRealnameProvider(cfg config.Config, logger *zap.Logger) realname.Provide
|
||||
}
|
||||
return provider
|
||||
default:
|
||||
return realname.NewMockProvider()
|
||||
return realname.NewMockProvider(encryptor)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user