ProductionConfigurationValidator.java

package org.darkroomlibrary.config;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;
import java.util.Locale;
import java.util.Set;

/**
 * Rejects known development credentials when the production profile is active.
 */
@Component
@Profile("prod")
public class ProductionConfigurationValidator implements InitializingBean {

    private static final Set<String> INSECURE_DATABASE_PASSWORDS = Set.of(
            "root",
            "password",
            "change-me",
            "replace-me",
            "darkroommysql@20606",
            "replace-with-a-strong-local-password"
    );
    private static final Set<String> INSECURE_JWT_SECRETS = Set.of(
            "dark-room-library-dev-jwt-secret-key-please-change-in-production",
            "dark-room-library-compose-jwt-secret-change-before-public-deployment",
            "at-least-32-random-bytes",
            "replace-with-at-least-32-random-bytes",
            "test-secret-key-for-unit-tests-only"
    );
    private static final Set<String> INSECURE_RABBIT_PASSWORDS = Set.of(
            "guest",
            "darkroomrabbit@20606",
            "replace-with-a-strong-local-password"
    );

    private final String databasePassword;
    private final String jwtSecret;
    private final boolean rabbitEnabled;
    private final String rabbitPassword;
    private final String notificationAlertWebhookUrl;

    public ProductionConfigurationValidator(
            @Value("${spring.datasource.password}") String databasePassword,
            @Value("${jwt.secret}") String jwtSecret,
            @Value("${middleware.rabbit.enabled:false}") boolean rabbitEnabled,
            @Value("${spring.rabbitmq.password:}") String rabbitPassword,
            @Value("${notification.alert.webhook-url:}") String notificationAlertWebhookUrl) {
        this.databasePassword = databasePassword;
        this.jwtSecret = jwtSecret;
        this.rabbitEnabled = rabbitEnabled;
        this.rabbitPassword = rabbitPassword;
        this.notificationAlertWebhookUrl = notificationAlertWebhookUrl;
    }

    @Override
    public void afterPropertiesSet() {
        rejectKnownValue("数据库密码", databasePassword, INSECURE_DATABASE_PASSWORDS);
        rejectKnownValue("JWT 密钥", jwtSecret, INSECURE_JWT_SECRETS);
        if (jwtSecret == null || jwtSecret.getBytes(StandardCharsets.UTF_8).length < 32) {
            throw new IllegalStateException("生产环境 JWT 密钥必须至少包含 32 个 UTF-8 字节");
        }
        if (rabbitEnabled) {
            rejectKnownValue("RabbitMQ 密码", rabbitPassword, INSECURE_RABBIT_PASSWORDS);
            if (notificationAlertWebhookUrl == null || notificationAlertWebhookUrl.isBlank()) {
                throw new IllegalStateException(
                        "生产环境启用 RabbitMQ 时必须配置通知与死信告警 Webhook");
            }
        }
    }

    private void rejectKnownValue(String name, String value, Set<String> insecureValues) {
        String normalized = value == null ? "" : value.trim().toLowerCase(Locale.ROOT);
        if (normalized.isEmpty() || insecureValues.contains(normalized)) {
            throw new IllegalStateException("生产环境不能使用空值或已知默认" + name);
        }
    }
}