seatonwan9
2025-08-28 e99dcf16787af69c69d16afcd92b6bf8922eede1
更新代码
6个文件已添加
1个文件已修改
292 ■■■■■ 已修改文件
src/main/java/com/webmanage/config/EncryptedPropertyDetector.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/webmanage/config/EncryptedPropertyEnvironmentPostProcessor.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/webmanage/util/AESUtil.java 136 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/META-INF/spring.factories 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/META-INF/spring/org.springframework.boot.env.EnvironmentPostProcessor 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-dev.yml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-prod.yml 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/webmanage/config/EncryptedPropertyDetector.java
New file
@@ -0,0 +1,24 @@
package com.webmanage.config;
import org.springframework.util.StringUtils;
/**
 * 检测配置值是否为加密格式的工具
 */
public class EncryptedPropertyDetector {
    private static final String PREFIX = "AES:";
    public static boolean isEncrypted(String value) {
        return StringUtils.hasText(value) && value.startsWith(PREFIX);
    }
    public static String stripPrefix(String value) {
        if (!isEncrypted(value)) {
            return value;
        }
        return value.substring(PREFIX.length());
    }
}
src/main/java/com/webmanage/config/EncryptedPropertyEnvironmentPostProcessor.java
New file
@@ -0,0 +1,55 @@
package com.webmanage.config;
import com.webmanage.util.AESUtil;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.util.StringUtils;
import java.util.HashMap;
import java.util.Map;
/**
 * 在应用启动早期对敏感配置进行解密
 */
public class EncryptedPropertyEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        Map<String, Object> overrides = new HashMap<>();
        // 扫描所有可枚举属性源,收集以 AES: 开头的属性值并解密,放入最高优先级覆盖源
        for (org.springframework.core.env.PropertySource<?> ps : environment.getPropertySources()) {
            if (ps instanceof EnumerablePropertySource) {
                EnumerablePropertySource<?> eps = (EnumerablePropertySource<?>) ps;
                for (String name : eps.getPropertyNames()) {
                    Object raw = eps.getProperty(name);
                    if (raw instanceof String) {
                        String value = (String) raw;
                        if (EncryptedPropertyDetector.isEncrypted(value)) {
                            String cipher = EncryptedPropertyDetector.stripPrefix(value);
                            String plain = AESUtil.decryptWithDefaultKey(cipher);
                            overrides.put(name, plain);
                        }
                    }
                }
            }
        }
        if (!overrides.isEmpty()) {
            MutablePropertySources sources = environment.getPropertySources();
            sources.addFirst(new MapPropertySource("decryptedSensitiveProperties", overrides));
        }
    }
    // 确保尽早执行
    @Override
    public int getOrder() {
        // 在配置文件加载完成后执行,避免拿不到值
        return Ordered.LOWEST_PRECEDENCE;
    }
}
src/main/java/com/webmanage/util/AESUtil.java
New file
@@ -0,0 +1,136 @@
package com.webmanage.util;
import com.webmanage.common.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
 * AES加密工具类
 *
 * @author August
 * @date 2022-07-29
 */
@Slf4j
public class AESUtil {
    private AESUtil() {
    }
    /**
     * AES_KEY
     */
    private static final String AES_KEY = "sD6i0YYW1a5FnzF6vRf13VtHocZabRcI";
    /**
     * GCM_IV
     */
    private static final String GCM_IV = "1SzB4YR0c0E9";
    /**
     * GCM_TAG长度
     */
    private static final int GCM_TAG_LENGTH = 16;
    /**
     * 加密
     *
     * @param plaintext 待加密文本
     * @param key       AES_KEY
     * @param iv        GCM_IV
     * @return 密文
     */
    public static String encrypt(String plaintext, byte[] key, byte[] iv) {
        if (StringUtils.isBlank(plaintext) || key == null || iv == null) {
            throw new BusinessException("加密参数不能为空");
        }
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
            GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec);
            // Perform Encryption
            byte[] cipherText = cipher.doFinal(plaintext.getBytes());
            StringBuilder sb = new StringBuilder();
            for (byte b : cipherText) {
                String hex = Integer.toHexString(b & 0xFF);
                if (hex.length() == 1) {
                    hex = '0' + hex;
                }
                sb.append(hex);
            }
            return sb.toString();
        } catch (Exception e) {
            throw new BusinessException("加密失败");
        }
    }
    /**
     * 解密
     *
     * @param cipherText 密文
     * @param key        AES_KEY
     * @param iv         GCM_IV
     * @return 解密结果
     */
    public static String decrypt(String cipherText, byte[] key, byte[] iv) {
        if (StringUtils.isBlank(cipherText) || key == null || iv == null) {
            throw new BusinessException("解密参数不能为空");
        }
        try {
            byte[] cipherTextByte = new byte[cipherText.length() / 2];
            for (int i = 0; i < cipherText.length() / 2; i++) {
                int high = Integer.parseInt(cipherText.substring(i * 2, i * 2 + 1),
                        16);
                int low = Integer.parseInt(
                        cipherText.substring(i * 2 + 1, i * 2 + 2), 16);
                cipherTextByte[i] = (byte) (high * 16 + low);
            }
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
            GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv);
            cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec);
            byte[] decryptedText = cipher.doFinal(cipherTextByte);
            return new String(decryptedText);
        } catch (Exception e) {
            throw new BusinessException("解密失败");
        }
    }
    /**
     * 使用默认密钥和IV进行解密
     * @param cipherText 以十六进制字符串表示的密文
     * @return 明文
     */
    public static String decryptWithDefaultKey(String cipherText) {
        return decrypt(cipherText, AES_KEY.getBytes(), GCM_IV.getBytes());
    }
    public static void main(String[] args) {
        // String plainText = "zynlpt2024";
        String plainText = "postgres";
        log.info("Original Text : " + plainText);
        String cipherText = encrypt(plainText, AES_KEY.getBytes(), GCM_IV.getBytes());
        log.info("Encrypted Text : " + cipherText);
        String decryptedText = decrypt(cipherText, AES_KEY.getBytes(), GCM_IV.getBytes());
        log.info("DeCrypted Text : " + decryptedText);
    }
}
src/main/resources/META-INF/spring.factories
New file
@@ -0,0 +1,2 @@
org.springframework.boot.env.EnvironmentPostProcessor=\
com.webmanage.config.EncryptedPropertyEnvironmentPostProcessor
src/main/resources/META-INF/spring/org.springframework.boot.env.EnvironmentPostProcessor
New file
@@ -0,0 +1,3 @@
com.webmanage.config.EncryptedPropertyEnvironmentPostProcessor
src/main/resources/application-dev.yml
@@ -5,7 +5,7 @@
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://localhost:5432/web_manage?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
    username: postgres
    password: zkyxpostgres
    password: AES:d9d2d3e0d586e76d02a97c451f3256bffdc806b4c7626904
    druid:
      # 初始连接数
      initial-size: 5
src/main/resources/application-prod.yml
New file
@@ -0,0 +1,70 @@
  # 数据源配置
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://192.168.20.52:5432/zypt-v2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
    username: postgres
    password: AES:f9d2d3e0d586e76d14aba9d24666acef98c4605be9ec680ac4cbeede7ad2
    druid:
      # 初始连接数
      initial-size: 5
      # 最小连接池数量
      min-idle: 10
      # 最大连接池数量
      max-active: 20
      # 配置获取连接等待超时的时间
      max-wait: 60000
      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      time-between-eviction-runs-millis: 60000
      # 配置一个连接在池中最小生存的时间,单位是毫秒
      min-evictable-idle-time-millis: 300000
      # 配置一个连接在池中最大生存的时间,单位是毫秒
      max-evictable-idle-time-millis: 900000
      # 配置检测连接是否有效
      validation-query: SELECT 1
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      # 打开PSCache,并且指定每个连接上PSCache的大小
      pool-prepared-statements: true
      max-pool-prepared-statement-per-connection-size: 20
      # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
      filters: stat,wall,slf4j
      # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
      connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
      # 配置DruidStatFilter
      web-stat-filter:
        enabled: true
        url-pattern: /*
        exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"
      # 配置DruidStatViewServlet
      stat-view-servlet:
        enabled: true
        url-pattern: /druid/*
        reset-enable: false
        login-username: admin
        login-password: 123456
  # Redis配置
  redis:
    host: 192.168.20.51
    port: 6379
    password: AES:c8d9cdfddcb4b32c677d657d2c8d56f9e7e4720832656637f5
    database: 4
    timeout: 10000ms
    lettuce:
      pool:
        max-active: 8
        max-wait: -1ms
        max-idle: 8
        min-idle: 0
# MinIO配置
minio:
   endpoint: http://192.168.20.52:9000
   access-key: minioadmin
   secret-key: AES:c4d4cefddd95e6733df755af0e29839c104ee8baa55eb53cdc24
   part-size: 104857600
   bucket-name: dev