CVE-2022-24861: Improper Input Validation (CWE-20)
value: Databasir is a team-oriented relational database model document management platform. Databasir 1.01 has remote code execution vulnerability. JDBC drivers are not validated prior to use and may be provided by users of the system. This can lead to code execution by any basic user who has access to the system. Users are advised to upgrade. There are no known workarounds to this issue.
CWE description: The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.
CVSS v3 base score: 8.8
Published: 2022-04-20T19:15Z
Repository: https://github.com/vran-dev/databasir
Language: Java
Files changed: api/src/main/resources/application-local.properties, api/src/main/resources/application.properties, core/src/main/java/com/databasir/core/domain/DomainErrors.java, core/src/main/java/com/databasir/core/domain/database/service/DatabaseTypeService.java, core/src/main/java/com/databasir/core/infrastructure/connection/CustomDatabaseConnectionFactory.java, core/src/main/java/com/databasir/core/infrastructure/driver/DriverResources.java, core/src/main/java/com/databasir/core/infrastructure/jwt/JwtTokens.java, core/src/test/java/com/databasir/core/domain/database/service/DatabaseTypeServiceTest.java, core/src/test/resources/application-ut.properties
Commit date: 2022-04-18 11:43:33 +0800
Commit message: fix some security bug (#103)

* fix: use hard-code secret

* feat: add driver class validate

* feat: optimize drvier resource code

* fix:ut failed
Diff: diff --git a/api/src/main/resources/application-local.properties b/api/src/main/resources/application-local.properties
index 9933c49..7d146b2 100644
--- a/api/src/main/resources/application-local.properties
+++ b/api/src/main/resources/application-local.properties
@@ -7,6 +7,7 @@ spring.flyway.baseline-on-migrate=true
 spring.flyway.locations=classpath:db/migration
 # db
 databasir.db.url=localhost:3306
 databasir.db.username=root
 databasir.db.password=123456
-databasir.db.driver-directory=drivers
\ No newline at end of file
+databasir.db.driver-directory=drivers
+databasir.jwt.secret=DatabasirJwtSecret
\ No newline at end of file
diff --git a/api/src/main/resources/application.properties b/api/src/main/resources/application.properties
index 89200d7..baf91d4 100644
--- a/api/src/main/resources/application.properties
+++ b/api/src/main/resources/application.properties
@@ -9,6 +9,7 @@ spring.jooq.sql-dialect=mysql
 # flyway
 spring.flyway.enabled=true
 spring.flyway.baseline-on-migrate=true
 spring.flyway.locations=classpath:db/migration
 # driver directory
-databasir.db.driver-directory=drivers
\ No newline at end of file
+databasir.db.driver-directory=drivers
+databasir.jwt.secret=${random.uuid}
\ No newline at end of file
diff --git a/core/src/main/java/com/databasir/core/domain/DomainErrors.java b/core/src/main/java/com/databasir/core/domain/DomainErrors.java
index 176c86e..a4fa7c9 100644
--- a/core/src/main/java/com/databasir/core/domain/DomainErrors.java
+++ b/core/src/main/java/com/databasir/core/domain/DomainErrors.java
@@ -42,11 +42,11 @@ public enum DomainErrors implements DatabasirErrors {
     MUST_NOT_REF_SELF("A_10026", "不能引用自身"),
     CIRCLE_REFERENCE("A_10027", "检查到循环引用"),
     DUPLICATE_COLUMN("A_10028", "重复的列"),
     INVALID_MOCK_DATA_SCRIPT("A_10029", "不合法的表达式"),
     CANNOT_DELETE_SELF("A_10030", "无法对自己执行删除账号操作"),
-    DRIVER_CLASS_NAME_OBTAIN_ERROR("A_10031", "获取驱动类名失败"),
+    DRIVER_CLASS_NOT_FOUND("A_10031", "获取驱动类名失败"),
     ;
 
     private final String errCode;
 
     private final String errMessage;
diff --git a/core/src/main/java/com/databasir/core/domain/database/service/DatabaseTypeService.java b/core/src/main/java/com/databasir/core/domain/database/service/DatabaseTypeService.java
index 773d99a..ceff088 100644
--- a/core/src/main/java/com/databasir/core/domain/database/service/DatabaseTypeService.java
+++ b/core/src/main/java/com/databasir/core/domain/database/service/DatabaseTypeService.java
@@ -34,10 +34,11 @@ public class DatabaseTypeService {
     private final ProjectDao projectDao;
 
     private final DatabaseTypePojoConverter databaseTypePojoConverter;
 
     public Integer create(DatabaseTypeCreateRequest request) {
+        driverResources.validateJar(request.getJdbcDriverFileUrl(), request.getJdbcDriverClassName());
         DatabaseTypePojo pojo = databaseTypePojoConverter.of(request);
         try {
             return databaseTypeDao.insertAndReturnId(pojo);
         } catch (DuplicateKeyException e) {
             throw DomainErrors.DATABASE_TYPE_NAME_DUPLICATE.exception();
@@ -48,22 +49,22 @@ public class DatabaseTypeService {
     public void update(DatabaseTypeUpdateRequest request) {
         databaseTypeDao.selectOptionalById(request.getId()).ifPresent(data -> {
             if (DatabaseTypes.has(data.getDatabaseType())) {
                 throw DomainErrors.MUST_NOT_MODIFY_SYSTEM_DEFAULT_DATABASE_TYPE.exception();
             }
-
+            driverResources.validateJar(request.getJdbcDriverFileUrl(), request.getJdbcDriverClassName());
             DatabaseTypePojo pojo = databaseTypePojoConverter.of(request);
             try {
                 databaseTypeDao.updateById(pojo);
             } catch (DuplicateKeyException e) {
                 throw DomainErrors.DATABASE_TYPE_NAME_DUPLICATE.exception();
             }
 
             // 名称修改，下载地址修改需要删除原有的 driver
             if (!Objects.equals(request.getDatabaseType(), data.getDatabaseType())
                     || !Objects.equals(request.getJdbcDriverFileUrl(), data.getJdbcDriverFileUrl())) {
-                driverResources.delete(data.getDatabaseType());
+                driverResources.deleteByDatabaseType(data.getDatabaseType());
             }
         });
 
     }
 
@@ -71,11 +72,11 @@ public class DatabaseTypeService {
         databaseTypeDao.selectOptionalById(id).ifPresent(data -> {
             if (DatabaseTypes.has(data.getDatabaseType())) {
                 throw DomainErrors.MUST_NOT_MODIFY_SYSTEM_DEFAULT_DATABASE_TYPE.exception();
             }
             databaseTypeDao.deleteById(id);
-            driverResources.delete(data.getDatabaseType());
+            driverResources.deleteByDatabaseType(data.getDatabaseType());
         });
     }
 
     public Page<DatabaseTypePageResponse> findByPage(Pageable page,
                                                      DatabaseTypePageCondition condition) {
@@ -107,9 +108,9 @@ public class DatabaseTypeService {
         return databaseTypeDao.selectOptionalById(id)
                 .map(databaseTypePojoConverter::toDetailResponse);
     }
 
     public String resolveDriverClassName(DriverClassNameResolveRequest request) {
-        return driverResources.resolveSqlDriverNameFromJar(request.getJdbcDriverFileUrl());
+        return driverResources.resolveDriverClassName(request.getJdbcDriverFileUrl());
     }
 
 }
diff --git a/core/src/main/java/com/databasir/core/infrastructure/connection/CustomDatabaseConnectionFactory.java b/core/src/main/java/com/databasir/core/infrastructure/connection/CustomDatabaseConnectionFactory.java
index 3e5d39c..f37df45 100644
--- a/core/src/main/java/com/databasir/core/infrastructure/connection/CustomDatabaseConnectionFactory.java
+++ b/core/src/main/java/com/databasir/core/infrastructure/connection/CustomDatabaseConnectionFactory.java
@@ -34,12 +34,14 @@ public class CustomDatabaseConnectionFactory implements DatabaseConnectionFactor
         return databaseTypeDao.existsByDatabaseType(databaseType);
     }
 
     @Override
     public Connection getConnection(Context context) throws SQLException {
-        DatabaseTypePojo type = databaseTypeDao.selectByDatabaseType(context.getDatabaseType());
-        File driverFile = driverResources.loadOrDownload(context.getDatabaseType(), type.getJdbcDriverFileUrl());
+        String databaseType = context.getDatabaseType();
+        DatabaseTypePojo type = databaseTypeDao.selectByDatabaseType(databaseType);
+        File driverFile = driverResources.loadOrDownloadByDatabaseType(databaseType, type.getJdbcDriverFileUrl());
+
         URLClassLoader loader = null;
         try {
             loader = new URLClassLoader(
                     new URL[]{
                             driverFile.toURI().toURL()
@@ -53,15 +55,15 @@ public class CustomDatabaseConnectionFactory implements DatabaseConnectionFactor
         // retrieve the driver class
 
         Class<?> clazz = null;
         Driver driver = null;
         try {
-            clazz = Class.forName(type.getJdbcDriverClassName(), true, loader);
+            clazz = Class.forName(type.getJdbcDriverClassName(), false, loader);
             driver = (Driver) clazz.getConstructor().newInstance();
         } catch (ClassNotFoundException e) {
             log.error("init driver error", e);
-            throw DomainErrors.CONNECT_DATABASE_FAILED.exception("驱动初始化异常, 请检查 Driver name：" + e.getMessage());
+            throw DomainErrors.CONNECT_DATABASE_FAILED.exception("驱动初始化异常, 请检查驱动类名：" + e.getMessage());
         } catch (InvocationTargetException
                  | InstantiationException
                  | IllegalAccessException
                  | NoSuchMethodException e) {
             log.error("init driver error", e);
diff --git a/core/src/main/java/com/databasir/core/infrastructure/driver/DriverResources.java b/core/src/main/java/com/databasir/core/infrastructure/driver/DriverResources.java
index cfa4780..a498287 100644
--- a/core/src/main/java/com/databasir/core/infrastructure/driver/DriverResources.java
+++ b/core/src/main/java/com/databasir/core/infrastructure/driver/DriverResources.java
@@ -2,20 +2,26 @@ package com.databasir.core.infrastructure.driver;
 
 import com.databasir.core.domain.DomainErrors;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.ClassUtils;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.http.HttpMethod;
 import org.springframework.stereotype.Component;
 import org.springframework.util.StreamUtils;
+import org.springframework.web.client.RestClientException;
 import org.springframework.web.client.RestTemplate;
 
 import java.io.*;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Optional;
 import java.util.UUID;
 import java.util.jar.JarFile;
 
 @Component
 @Slf4j
@@ -25,52 +31,87 @@ public class DriverResources {
     @Value("${databasir.db.driver-directory}")
     private String driverBaseDirectory;
 
     private final RestTemplate restTemplate;
 
-    public void delete(String databaseType) {
+    public void deleteByDatabaseType(String databaseType) {
         Path path = Paths.get(driverFilePath(driverBaseDirectory, databaseType));
         try {
             Files.deleteIfExists(path);
         } catch (IOException e) {
             log.error("delete driver error " + databaseType, e);
         }
     }
 
-    public String resolveSqlDriverNameFromJar(String driverFileUrl) {
+    public Optional<File> loadByDatabaseType(String databaseType) {
+        Path path = Paths.get(driverFilePath(driverBaseDirectory, databaseType));
+        if (Files.exists(path)) {
+            return Optional.of(path.toFile());
+        } else {
+            return Optional.empty();
+        }
+    }
+
+    public File loadOrDownloadByDatabaseType(String databaseType, String driverFileUrl) {
+        return loadByDatabaseType(databaseType)
+                .orElseGet(() -> download(driverFileUrl, driverFilePath(driverBaseDirectory, databaseType)));
+    }
+
+    public String resolveDriverClassName(String driverFileUrl) {
         String tempFilePath = "temp/" + UUID.randomUUID() + ".jar";
-        File driverFile = doDownload(driverFileUrl, tempFilePath);
-        String className = doResolveSqlDriverNameFromJar(driverFile);
+        File driverFile = download(driverFileUrl, tempFilePath);
+        String className = resolveDriverClassName(driverFile);
         try {
             Files.deleteIfExists(driverFile.toPath());
         } catch (IOException e) {
             log.error("delete driver error " + tempFilePath, e);
         }
         return className;
     }
 
-    public File loadOrDownload(String databaseType, String driverFileUrl) {
-        String filePath = driverFilePath(driverBaseDirectory, databaseType);
-        Path path = Path.of(filePath);
-        if (Files.exists(path)) {
-            // ignore
-            log.debug("{} already exists, ignore download from {}", filePath, driverFileUrl);
-            return path.toFile();
+    public String resolveDriverClassName(File driverFile) {
+        JarFile jarFile = null;
+        try {
+            jarFile = new JarFile(driverFile);
+        } catch (IOException e) {
+            log.error("resolve driver class name error", e);
+            throw DomainErrors.DRIVER_CLASS_NOT_FOUND.exception(e.getMessage());
         }
-        return this.doDownload(driverFileUrl, filePath);
+
+        final JarFile driverJar = jarFile;
+        String driverClassName = jarFile.stream()
+                .filter(entry -> entry.getName().contains("META-INF/services/java.sql.Driver"))
+                .findFirst()
+                .map(entry -> {
+                    InputStream stream = null;
+                    BufferedReader reader = null;
+                    try {
+                        stream = driverJar.getInputStream(entry);
+                        reader = new BufferedReader(new InputStreamReader(stream));
+                        return reader.readLine();
+                    } catch (IOException e) {
+                        log.error("resolve driver class name error", e);
+                        throw DomainErrors.DRIVER_CLASS_NOT_FOUND.exception(e.getMessage());
+                    } finally {
+                        IOUtils.closeQuietly(reader, ex -> log.error("close reader error", ex));
+                    }
+                })
+                .orElseThrow(DomainErrors.DRIVER_CLASS_NOT_FOUND::exception);
+        IOUtils.closeQuietly(jarFile, ex -> log.error("close jar file error", ex));
+        return driverClassName;
     }
 
-    private File doDownload(String driverFileUrl, String filePath) {
-        Path path = Path.of(filePath);
+    private File download(String driverFileUrl, String targetFile) {
+        Path path = Path.of(targetFile);
 
         // create parent directory
         if (Files.notExists(path)) {
             path.getParent().toFile().mkdirs();
             try {
                 Files.createFile(path);
             } catch (IOException e) {
-                log.error("create file error " + filePath, e);
+                log.error("create file error " + targetFile, e);
                 throw DomainErrors.DOWNLOAD_DRIVER_ERROR.exception(e.getMessage());
             }
         }
 
         // download
@@ -79,56 +120,59 @@ public class DriverResources {
                 if (response.getStatusCode().is2xxSuccessful()) {
                     File file = path.toFile();
                     FileOutputStream out = new FileOutputStream(file);
                     StreamUtils.copy(response.getBody(), out);
                     IOUtils.closeQuietly(out, ex -> log.error("close file error", ex));
-                    log.info("{} download success ", filePath);
+                    log.info("{} download success ", targetFile);
                     return file;
                 } else {
-                    log.error("{} download error from {}: {} ", filePath, driverFileUrl, response);
+                    log.error("{} download error from {}: {} ", targetFile, driverFileUrl, response);
                     throw DomainErrors.DOWNLOAD_DRIVER_ERROR.exception("驱动下载失败："
                             + response.getStatusCode()
                             + ", "
                             + response.getStatusText());
                 }
             });
-        } catch (IllegalArgumentException e) {
-            log.error(filePath + " download driver error", e);
+        } catch (RestClientException e) {
+            log.error(targetFile + " download driver error", e);
             throw DomainErrors.DOWNLOAD_DRIVER_ERROR.exception(e.getMessage());
         }
     }
 
-    private String doResolveSqlDriverNameFromJar(File driverFile) {
-        JarFile jarFile = null;
+    public void validateJar(String driverFileUrl, String className) {
+        String tempFilePath = "temp/" + UUID.randomUUID() + ".jar";
+        File driverFile = download(driverFileUrl, tempFilePath);
+        URLClassLoader loader = null;
         try {
-            jarFile = new JarFile(driverFile);
-        } catch (IOException e) {
-            log.error("resolve driver class name error", e);
-            throw DomainErrors.DRIVER_CLASS_NAME_OBTAIN_ERROR.exception(e.getMessage());
+            loader = new URLClassLoader(
+                    new URL[]{driverFile.toURI().toURL()},
+                    this.getClass().getClassLoader()
+            );
+        } catch (MalformedURLException e) {
+            log.error("load driver jar error ", e);
+            throw DomainErrors.DOWNLOAD_DRIVER_ERROR.exception(e.getMessage());
         }
 
-        final JarFile driverJar = jarFile;
-        String driverClassName = jarFile.stream()
-                .filter(entry -> entry.getName().contains("META-INF/services/java.sql.Driver"))
-                .findFirst()
-                .map(entry -> {
-                    InputStream stream = null;
-                    BufferedReader reader = null;
-                    try {
-                        stream = driverJar.getInputStream(entry);
-                        reader = new BufferedReader(new InputStreamReader(stream));
-                        return reader.readLine();
-                    } catch (IOException e) {
-                        log.error("resolve driver class name error", e);
-                        throw DomainErrors.DRIVER_CLASS_NAME_OBTAIN_ERROR.exception(e.getMessage());
-                    } finally {
-                        IOUtils.closeQuietly(reader, ex -> log.error("close reader error", ex));
-                    }
-                })
-                .orElseThrow(DomainErrors.DRIVER_CLASS_NAME_OBTAIN_ERROR::exception);
-        IOUtils.closeQuietly(jarFile, ex -> log.error("close jar file error", ex));
-        return driverClassName;
+        try {
+            Class clazz = Class.forName(className, false, loader);
+            boolean isValid = ClassUtils.getAllInterfaces(clazz)
+                    .stream()
+                    .anyMatch(cls -> cls.getName().equals("java.sql.Driver"));
+            if (!isValid) {
+                throw DomainErrors.DRIVER_CLASS_NOT_FOUND.exception("不合法的驱动类，请重新指定");
+            }
+        } catch (ClassNotFoundException e) {
+            log.error("init driver error", e);
+            throw DomainErrors.DRIVER_CLASS_NOT_FOUND.exception("驱动初始化异常, 请检查驱动类名：" + e.getMessage());
+        } finally {
+            IOUtils.closeQuietly(loader);
+            try {
+                Files.deleteIfExists(driverFile.toPath());
+            } catch (IOException e) {
+                log.error("delete driver error " + tempFilePath, e);
+            }
+        }
     }
 
     private String driverFilePath(String baseDir, String databaseType) {
         String fileName = databaseType + ".jar";
         String filePath;
diff --git a/core/src/main/java/com/databasir/core/infrastructure/jwt/JwtTokens.java b/core/src/main/java/com/databasir/core/infrastructure/jwt/JwtTokens.java
index 81fbcb7..e3e046f 100644
--- a/core/src/main/java/com/databasir/core/infrastructure/jwt/JwtTokens.java
+++ b/core/src/main/java/com/databasir/core/infrastructure/jwt/JwtTokens.java
@@ -3,10 +3,11 @@ package com.databasir.core.infrastructure.jwt;
 import com.auth0.jwt.JWT;
 import com.auth0.jwt.algorithms.Algorithm;
 import com.auth0.jwt.exceptions.JWTVerificationException;
 import com.auth0.jwt.interfaces.JWTVerifier;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;
 
 import java.time.Instant;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
@@ -21,24 +22,25 @@ public class JwtTokens {
 
     public static final String TOKEN_PREFIX = "Bearer ";
 
     private static final String ISSUER = "Databasir";
 
-    private static final String SECRET = "Databasir2022";
+    @Value("${databasir.jwt.secret}")
+    private String tokenSecret;
 
     public String accessToken(String username) {
-        Algorithm algorithm = Algorithm.HMAC256(SECRET);
+        Algorithm algorithm = Algorithm.HMAC256(tokenSecret);
 
         return JWT.create()
                 .withExpiresAt(new Date(new Date().getTime() + ACCESS_EXPIRE_TIME))
                 .withIssuer(ISSUER)
                 .withClaim("username", username)
                 .sign(algorithm);
     }
 
     public boolean verify(String token) {
-        JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET))
+        JWTVerifier verifier = JWT.require(Algorithm.HMAC256(tokenSecret))
                 .withIssuer(ISSUER)
                 .build();
         try {
             verifier.verify(token);
             return true;
diff --git a/core/src/test/java/com/databasir/core/domain/database/service/DatabaseTypeServiceTest.java b/core/src/test/java/com/databasir/core/domain/database/service/DatabaseTypeServiceTest.java
index ed2d134..8d03586 100644
--- a/core/src/test/java/com/databasir/core/domain/database/service/DatabaseTypeServiceTest.java
+++ b/core/src/test/java/com/databasir/core/domain/database/service/DatabaseTypeServiceTest.java
@@ -3,27 +3,41 @@ package com.databasir.core.domain.database.service;
 import com.databasir.common.DatabasirException;
 import com.databasir.core.BaseTest;
 import com.databasir.core.domain.DomainErrors;
 import com.databasir.core.domain.database.data.DatabaseTypeCreateRequest;
 import com.databasir.core.domain.database.data.DatabaseTypeUpdateRequest;
+import com.databasir.core.infrastructure.driver.DriverResources;
 import com.databasir.dao.impl.DatabaseTypeDao;
 import com.databasir.dao.tables.pojos.DatabaseTypePojo;
 import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.test.context.jdbc.Sql;
 import org.springframework.transaction.annotation.Transactional;
 
+import static org.mockito.ArgumentMatchers.anyString;
+
 @Transactional
 class DatabaseTypeServiceTest extends BaseTest {
 
     @Autowired
     private DatabaseTypeService databaseTypeService;
 
     @Autowired
     private DatabaseTypeDao databaseTypeDao;
 
+    @MockBean
+    private DriverResources driverResources;
+
+    @BeforeEach
+    public void setUp() {
+        Mockito.doNothing().when(driverResources).validateJar(anyString(), anyString());
+    }
+
     @Test
     void create() {
         DatabaseTypeCreateRequest request = new DatabaseTypeCreateRequest();
         request.setDatabaseType("ut-mysql");
         request.setIcon("");
diff --git a/core/src/test/resources/application-ut.properties b/core/src/test/resources/application-ut.properties
index e339f38..4547eda 100644
--- a/core/src/test/resources/application-ut.properties
+++ b/core/src/test/resources/application-ut.properties
@@ -13,6 +13,7 @@ spring.flyway.baseline-on-migrate=true
 spring.flyway.locations=classpath:db/migration
 # db
 databasir.db.url=localhost:3306
 databasir.db.username=root
 databasir.db.password=123456
-databasir.db.driver-directory=drivers
\ No newline at end of file
+databasir.db.driver-directory=drivers
+databasir.jwt.secret=DatabasirJwtSecret
\ No newline at end of file

Vulnerable code: databasir.db.driver-directory=drivers
databasir.db.driver-directory=drivers
    DRIVER_CLASS_NAME_OBTAIN_ERROR("A_10031", "获取驱动类名失败"),

                driverResources.delete(data.getDatabaseType());
            driverResources.delete(data.getDatabaseType());
        return driverResources.resolveSqlDriverNameFromJar(request.getJdbcDriverFileUrl());
        DatabaseTypePojo type = databaseTypeDao.selectByDatabaseType(context.getDatabaseType());
        File driverFile = driverResources.loadOrDownload(context.getDatabaseType(), type.getJdbcDriverFileUrl());
            clazz = Class.forName(type.getJdbcDriverClassName(), true, loader);
            throw DomainErrors.CONNECT_DATABASE_FAILED.exception("驱动初始化异常, 请检查 Driver name：" + e.getMessage());
    public void delete(String databaseType) {
    public String resolveSqlDriverNameFromJar(String driverFileUrl) {
        File driverFile = doDownload(driverFileUrl, tempFilePath);
        String className = doResolveSqlDriverNameFromJar(driverFile);
    public File loadOrDownload(String databaseType, String driverFileUrl) {
        String filePath = driverFilePath(driverBaseDirectory, databaseType);
        Path path = Path.of(filePath);
        if (Files.exists(path)) {
            // ignore
            log.debug("{} already exists, ignore download from {}", filePath, driverFileUrl);
            return path.toFile();
        return this.doDownload(driverFileUrl, filePath);
    private File doDownload(String driverFileUrl, String filePath) {
        Path path = Path.of(filePath);
                log.error("create file error " + filePath, e);
                    log.info("{} download success ", filePath);
                    log.error("{} download error from {}: {} ", filePath, driverFileUrl, response);
        } catch (IllegalArgumentException e) {
            log.error(filePath + " download driver error", e);
    private String doResolveSqlDriverNameFromJar(File driverFile) {
        JarFile jarFile = null;
            jarFile = new JarFile(driverFile);
        } catch (IOException e) {
            log.error("resolve driver class name error", e);
            throw DomainErrors.DRIVER_CLASS_NAME_OBTAIN_ERROR.exception(e.getMessage());
        final JarFile driverJar = jarFile;
        String driverClassName = jarFile.stream()
                .filter(entry -> entry.getName().contains("META-INF/services/java.sql.Driver"))
                .findFirst()
                .map(entry -> {
                    InputStream stream = null;
                    BufferedReader reader = null;
                    try {
                        stream = driverJar.getInputStream(entry);
                        reader = new BufferedReader(new InputStreamReader(stream));
                        return reader.readLine();
                    } catch (IOException e) {
                        log.error("resolve driver class name error", e);
                        throw DomainErrors.DRIVER_CLASS_NAME_OBTAIN_ERROR.exception(e.getMessage());
                    } finally {
                        IOUtils.closeQuietly(reader, ex -> log.error("close reader error", ex));
                    }
                })
                .orElseThrow(DomainErrors.DRIVER_CLASS_NAME_OBTAIN_ERROR::exception);
        IOUtils.closeQuietly(jarFile, ex -> log.error("close jar file error", ex));
        return driverClassName;
    private static final String SECRET = "Databasir2022";
        Algorithm algorithm = Algorithm.HMAC256(SECRET);
        JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET))
databasir.db.driver-directory=drivers
Fixed code: databasir.db.driver-directory=drivers
databasir.jwt.secret=DatabasirJwtSecret
databasir.db.driver-directory=drivers
databasir.jwt.secret=${random.uuid}
    DRIVER_CLASS_NOT_FOUND("A_10031", "获取驱动类名失败"),
        driverResources.validateJar(request.getJdbcDriverFileUrl(), request.getJdbcDriverClassName());
            driverResources.validateJar(request.getJdbcDriverFileUrl(), request.getJdbcDriverClassName());
                driverResources.deleteByDatabaseType(data.getDatabaseType());
            driverResources.deleteByDatabaseType(data.getDatabaseType());
        return driverResources.resolveDriverClassName(request.getJdbcDriverFileUrl());
        String databaseType = context.getDatabaseType();
        DatabaseTypePojo type = databaseTypeDao.selectByDatabaseType(databaseType);
        File driverFile = driverResources.loadOrDownloadByDatabaseType(databaseType, type.getJdbcDriverFileUrl());

            clazz = Class.forName(type.getJdbcDriverClassName(), false, loader);
            throw DomainErrors.CONNECT_DATABASE_FAILED.exception("驱动初始化异常, 请检查驱动类名：" + e.getMessage());
import org.apache.commons.lang3.ClassUtils;
import org.springframework.web.client.RestClientException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Optional;
    public void deleteByDatabaseType(String databaseType) {
    public Optional<File> loadByDatabaseType(String databaseType) {
        Path path = Paths.get(driverFilePath(driverBaseDirectory, databaseType));
        if (Files.exists(path)) {
            return Optional.of(path.toFile());
        } else {
            return Optional.empty();
        }
    }

    public File loadOrDownloadByDatabaseType(String databaseType, String driverFileUrl) {
        return loadByDatabaseType(databaseType)
                .orElseGet(() -> download(driverFileUrl, driverFilePath(driverBaseDirectory, databaseType)));
    }

    public String resolveDriverClassName(String driverFileUrl) {
        File driverFile = download(driverFileUrl, tempFilePath);
        String className = resolveDriverClassName(driverFile);
    public String resolveDriverClassName(File driverFile) {
        JarFile jarFile = null;
        try {
            jarFile = new JarFile(driverFile);
        } catch (IOException e) {
            log.error("resolve driver class name error", e);
            throw DomainErrors.DRIVER_CLASS_NOT_FOUND.exception(e.getMessage());

        final JarFile driverJar = jarFile;
        String driverClassName = jarFile.stream()
                .filter(entry -> entry.getName().contains("META-INF/services/java.sql.Driver"))
                .findFirst()
                .map(entry -> {
                    InputStream stream = null;
                    BufferedReader reader = null;
                    try {
                        stream = driverJar.getInputStream(entry);
                        reader = new BufferedReader(new InputStreamReader(stream));
                        return reader.readLine();
                    } catch (IOException e) {
                        log.error("resolve driver class name error", e);
                        throw DomainErrors.DRIVER_CLASS_NOT_FOUND.exception(e.getMessage());
                    } finally {
                        IOUtils.closeQuietly(reader, ex -> log.error("close reader error", ex));
                    }
                })
                .orElseThrow(DomainErrors.DRIVER_CLASS_NOT_FOUND::exception);
        IOUtils.closeQuietly(jarFile, ex -> log.error("close jar file error", ex));
        return driverClassName;
    private File download(String driverFileUrl, String targetFile) {
        Path path = Path.of(targetFile);
                log.error("create file error " + targetFile, e);
                    log.info("{} download success ", targetFile);
                    log.error("{} download error from {}: {} ", targetFile, driverFileUrl, response);
        } catch (RestClientException e) {
            log.error(targetFile + " download driver error", e);
    public void validateJar(String driverFileUrl, String className) {
        String tempFilePath = "temp/" + UUID.randomUUID() + ".jar";
        File driverFile = download(driverFileUrl, tempFilePath);
        URLClassLoader loader = null;
            loader = new URLClassLoader(
                    new URL[]{driverFile.toURI().toURL()},
                    this.getClass().getClassLoader()
            );
        } catch (MalformedURLException e) {
            log.error("load driver jar error ", e);
            throw DomainErrors.DOWNLOAD_DRIVER_ERROR.exception(e.getMessage());
        try {
            Class clazz = Class.forName(className, false, loader);
            boolean isValid = ClassUtils.getAllInterfaces(clazz)
                    .stream()
                    .anyMatch(cls -> cls.getName().equals("java.sql.Driver"));
            if (!isValid) {
                throw DomainErrors.DRIVER_CLASS_NOT_FOUND.exception("不合法的驱动类，请重新指定");
            }
        } catch (ClassNotFoundException e) {
            log.error("init driver error", e);
            throw DomainErrors.DRIVER_CLASS_NOT_FOUND.exception("驱动初始化异常, 请检查驱动类名：" + e.getMessage());
        } finally {
            IOUtils.closeQuietly(loader);
            try {
                Files.deleteIfExists(driverFile.toPath());
            } catch (IOException e) {
                log.error("delete driver error " + tempFilePath, e);
            }
        }
import org.springframework.beans.factory.annotation.Value;
    @Value("${databasir.jwt.secret}")
    private String tokenSecret;
        Algorithm algorithm = Algorithm.HMAC256(tokenSecret);
        JWTVerifier verifier = JWT.require(Algorithm.HMAC256(tokenSecret))
import com.databasir.core.infrastructure.driver.DriverResources;
import org.junit.jupiter.api.BeforeEach;
import org.mockito.Mockito;
import org.springframework.boot.test.mock.mockito.MockBean;
import static org.mockito.ArgumentMatchers.anyString;

    @MockBean
    private DriverResources driverResources;

    @BeforeEach
    public void setUp() {
        Mockito.doNothing().when(driverResources).validateJar(anyString(), anyString());
    }

databasir.db.driver-directory=drivers
databasir.jwt.secret=DatabasirJwtSecret