Java安全配置管理最佳实践
1. 配置文件安全基础
1.1 配置文件分离
在开发过程中,应该将配置文件按环境和敏感程度分离:
src/
├── main/
│ ├── resources/
│ │ ├── application.yml // 基础配置
│ │ ├── application-dev.yml // 开发环境配置
│ │ ├── application-test.yml // 测试环境配置
│ │ └── application-prod.yml // 生产环境配置
│ └── java/
└── test/
1.2 安全配置示例
# application.yml - 基础配置文件
spring:
profiles:
active: @profile.active@ # 使用Maven配置文件来控制激活的环境
# 日志配置
logging:
level:
root: INFO
com.example: DEBUG
file:
path: /var/log/myapp/
# 安全配置
security:
headers:
frame: DENY
xss: ON
content-type: ON
2. 敏感信息处理
2.1 配置加密工具类
public class ConfigEncryptUtil {
private static final String ALGORITHM = "AES/GCM/NoPadding";
private static final int TAG_LENGTH_BIT = 128;
private static final int IV_LENGTH_BYTE = 12;
public static String encrypt(String value, String key) throws Exception {
byte[] iv = generateRandomIV();
SecretKey secretKey = generateKey(key);
Cipher cipher = Cipher.getInstance(ALGORITHM);
GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, iv);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, spec);
byte[] encryptedData = cipher.doFinal(value.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(concatenate(iv, encryptedData));
}
public static String decrypt(String encryptedValue, String key) throws Exception {
byte[] decoded = Base64.getDecoder().decode(encryptedValue);
byte[] iv = Arrays.copyOfRange(decoded, 0, IV_LENGTH_BYTE);
byte[] cipherText = Arrays.copyOfRange(decoded, IV_LENGTH_BYTE, decoded.length);
SecretKey secretKey = generateKey(key);
Cipher cipher = Cipher.getInstance(ALGORITHM);
GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, iv);
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec);
return new String(cipher.doFinal(cipherText), StandardCharsets.UTF_8);
}
private static byte[] generateRandomIV() {
byte[] iv = new byte[IV_LENGTH_BYTE];
new SecureRandom().nextBytes(iv);
return iv;
}
private static SecretKey generateKey(String key) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(key.getBytes(StandardCharsets.UTF_8));
return new SecretKeySpec(hash, "AES");
}
private static byte[] concatenate(byte[] a, byte[] b) {
byte[] result = Arrays.copyOf(a, a.length + b.length);
System.arraycopy(b, 0, result, a.length, b.length);
return result;
}
}
2.2 配置加载器
@Configuration
public class SecureConfigurationLoader {
private static final Logger logger = LoggerFactory.getLogger(SecureConfigurationLoader.class);
@Value("${config.encryption.key}")
private String encryptionKey;
@Bean
public Properties secureProperties() {
Properties properties = new Properties();
try {
loadEncryptedProperties(properties);
} catch (Exception e) {
logger.error("Failed to load secure properties", e);
throw new RuntimeException("Configuration loading failed", e);
}
return properties;
}
private void loadEncryptedProperties(Properties properties) throws Exception {
try (InputStream input = getClass().getClassLoader().getResourceAsStream("secure.properties")) {
Properties encryptedProps = new Properties();
encryptedProps.load(input);
for (String key : encryptedProps.stringPropertyNames()) {
String encryptedValue = encryptedProps.getProperty(key);
if (encryptedValue.startsWith("ENC(") && encryptedValue.endsWith(")")) {
String value = encryptedValue.substring(4, encryptedValue.length() - 1);
properties.setProperty(key, ConfigEncryptUtil.decrypt(value, encryptionKey));
} else {
properties.setProperty(key, encryptedValue);
}
}
}
}
}
3. 配置验证
3.1 配置验证器
@Component
public class ConfigurationValidator {
private static final Logger logger = LoggerFactory.getLogger(ConfigurationValidator.class);
@PostConstruct
public void validateConfiguration() {
validateDatabaseConfig();
validateSecurityConfig();
validateFilePermissions();
}
private void validateDatabaseConfig() {
// 验证数据库配置
try {
validateRequiredProperties("spring.datasource.url",
"spring.datasource.username");
validateConnectionPool();
} catch (Exception e) {
logger.error("Database configuration validation failed", e);
throw new ConfigurationException("Invalid database configuration", e);
}
}
private void validateSecurityConfig() {
// 验证安全配置
try {
validateRequiredProperties("security.headers.frame",
"security.headers.xss");
validateTlsVersion();
} catch (Exception e) {
logger.error("Security configuration validation failed", e);
throw new ConfigurationException("Invalid security configuration", e);
}
}
private void validateFilePermissions() {
// 验证文件权限
String logPath = environment.getProperty("logging.file.path");
if (logPath != null) {
File logDir = new File(logPath);
if (!logDir.exists() || !logDir.canWrite()) {
throw new ConfigurationException("Log directory is not writable: " + logPath);
}
}
}
}
4. 运行时配置管理
4.1 配置更新监听器
@Component
public class ConfigurationChangeListener {
private final ApplicationEventPublisher eventPublisher;
@Autowired
public ConfigurationChangeListener(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
@Scheduled(fixedDelay = 60000) // 每分钟检查一次
public void checkConfigurationChanges() {
if (hasConfigurationChanged()) {
eventPublisher.publishEvent(new ConfigurationChangeEvent(this));
}
}
private boolean hasConfigurationChanged() {
// 实现配置文件变更检测逻辑
return false; // 示例返回
}
}
@Component
public class ConfigurationChangeHandler {
@EventListener
public void handleConfigurationChange(ConfigurationChangeEvent event) {
// 处理配置变更
reloadConfiguration();
updateServices();
}
}
5. 最佳实践总结
配置分离
- 不同环境使用不同的配置文件
- 敏感配置与普通配置分开存储
- 使用环境变量或外部配置系统存储敏感信息
加密存储
- 使用强加密算法保护敏感配置
- 密钥管理与配置分开
- 定期轮换加密密钥
访问控制
- 限制配置文件的访问权限
- 实施最小权限原则
- 记录配置访问日志
配置验证
- 启动时验证所有必要配置
- 定期验证配置有效性
- 实施配置变更监控
审计与监控
- 记录所有配置变更
- 实施配置变更审批流程
- 监控异常配置访问
通过实施以上最佳实践,可以显著提高应用程序配置的安全性和可维护性。