fix(web-ai): store encryption key in database

This commit is contained in:
2026-07-19 20:20:57 +08:00
parent c47bf748cb
commit 6853facbe0
7 changed files with 56 additions and 23 deletions
@@ -11,7 +11,6 @@ public class AihrWebAiProperties {
private String provider = "tavily";
private String endpoint = "https://api.tavily.com/search";
private String apiKey = "";
private String secretKey = "";
private int maxResults = 5;
public boolean isEnabled() {
@@ -46,14 +45,6 @@ public class AihrWebAiProperties {
this.apiKey = apiKey;
}
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
public int getMaxResults() {
return maxResults;
}
@@ -2,6 +2,7 @@ package org.dromara.aihr.webai;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.exception.ServiceException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import javax.crypto.Cipher;
@@ -17,9 +18,11 @@ import java.util.Base64;
class AihrWebSecretCodec {
private static final String PREFIX = "enc:v1:";
private static final String CONFIG_KEY = "provider-encryption-v1";
private static final SecureRandom RANDOM = new SecureRandom();
private final AihrWebAiProperties properties;
private final JdbcTemplate jdbcTemplate;
private volatile SecretKeySpec databaseKey;
String encrypt(String plaintext) {
if (plaintext == null || plaintext.isBlank()) {
@@ -69,11 +72,36 @@ class AihrWebSecretCodec {
}
private SecretKeySpec key() throws Exception {
String secret = properties.getSecretKey() == null ? "" : properties.getSecretKey().trim();
if (secret.length() < 16) {
throw new ServiceException("请先配置至少16位的 AIHR_WEB_AI_SECRET_KEY");
if (databaseKey != null) {
return databaseKey;
}
synchronized (this) {
if (databaseKey != null) {
return databaseKey;
}
jdbcTemplate.execute("""
CREATE TABLE IF NOT EXISTS `aihr_web_ai_secret` (
`config_key` varchar(64) NOT NULL,
`config_value` varchar(255) NOT NULL,
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`config_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
""");
byte[] random = new byte[32];
RANDOM.nextBytes(random);
jdbcTemplate.update("""
insert ignore into aihr_web_ai_secret (config_key, config_value, create_time)
values (?, ?, now())
""", CONFIG_KEY, Base64.getEncoder().encodeToString(random));
String secret = jdbcTemplate.queryForObject("""
select config_value from aihr_web_ai_secret where config_key = ?
""", String.class, CONFIG_KEY);
if (secret == null || secret.length() < 16) {
throw new ServiceException("全网检索数据库主密钥无效");
}
byte[] digest = MessageDigest.getInstance("SHA-256").digest(secret.getBytes(StandardCharsets.UTF_8));
databaseKey = new SecretKeySpec(digest, "AES");
return databaseKey;
}
byte[] digest = MessageDigest.getInstance("SHA-256").digest(secret.getBytes(StandardCharsets.UTF_8));
return new SecretKeySpec(digest, "AES");
}
}
@@ -22,6 +22,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -172,9 +174,10 @@ class AihrWebAiSecurityTest {
@Test
@Tag("dev")
void databaseProviderSecretUsesAuthenticatedEncryption() {
AihrWebAiProperties properties = new AihrWebAiProperties();
properties.setSecretKey("unit-test-secret-key-32-bytes-long");
AihrWebSecretCodec codec = new AihrWebSecretCodec(properties);
JdbcTemplate jdbcTemplate = mock(JdbcTemplate.class);
when(jdbcTemplate.queryForObject(anyString(), eq(String.class), eq("provider-encryption-v1")))
.thenReturn("unit-test-secret-key-32-bytes-long");
AihrWebSecretCodec codec = new AihrWebSecretCodec(jdbcTemplate);
String encrypted = codec.encrypt("tvly-test-secret");
@@ -0,0 +1,10 @@
-- 全网 AI 提供方密钥的数据库主密钥;不通过环境变量或通用参数接口暴露。
CREATE TABLE IF NOT EXISTS `aihr_web_ai_secret` (
`config_key` varchar(64) NOT NULL COMMENT '内部配置键',
`config_value` varchar(255) NOT NULL COMMENT '随机主密钥,仅后端读取',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`config_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='全网AI内部密钥配置';
INSERT IGNORE INTO `aihr_web_ai_secret` (`config_key`, `config_value`)
VALUES ('provider-encryption-v1', TO_BASE64(RANDOM_BYTES(32)));