首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

MyBatis 在 C/S 架構中的應用實踐:加密配置與 SqlSessionFactory 管理

     編輯:Java 相關知識

背景與挑戰

在傳統 C/S(Client/Server)架構的 Java 桌面應用中,客戶端直連數據庫,如何妥善保護數據庫連接配置(尤其是用戶名/密碼)是關鍵安全課題。本文介紹一種將 MyBatis XML 配置文件加密存儲、運行時解密加載的方案,並展示單例模式管理 SqlSessionFactory 的最佳實踐。

方案架構

  1. 開發期:編寫標準 mybatis-config.xml,包含數據源、映射器等配置。
  2. 打包前:使用對稱加密算法(如 DES/AES)將 XML 內容加密為二進制文件(如 basic.data),放入 src 目錄隨 JAR 發布。
  3. 運行期:啟動時讀取加密文件 → 解密 → 轉為 InputStream → 傳給 SqlSessionFactoryBuilder.build()。

核心代碼實現

1. 加密配置文件示例(加密前的 XML)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
        <property name="username" value="app_user"/>
        <property name="password" value="plain_password"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="com/example/dao/UserMapper.xml"/>
  </mappers>
</configuration>

2. DES 加密/解密工具類(簡化版)

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import java.security.SecureRandom;
import java.util.Base64;

public class DesUtil {
    private static final String ALGORITHM = "DES";
    private static final String KEY = "MyBatisKey"; // 8 字節密鑰,生產環境應從安全存儲讀取

    public static String encrypt(String data) throws Exception {
        SecureRandom sr = new SecureRandom();
        DESKeySpec dks = new DESKeySpec(KEY.getBytes());
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM);
        SecretKey secretKey = keyFactory.generateSecret(dks);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, sr);
        byte[] encrypted = cipher.doFinal(data.getBytes("UTF-8"));
        return Base64.getEncoder().encodeToString(encrypted);
    }

    public static String decrypt(String encryptedData) throws Exception {
        SecureRandom sr = new SecureRandom();
        DESKeySpec dks = new DESKeySpec(KEY.getBytes());
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM);
        SecretKey secretKey = keyFactory.generateSecret(dks);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, secretKey, sr);
        byte[] decoded = Base64.getDecoder().decode(encryptedData);
        byte[] decrypted = cipher.doFinal(decoded);
        return new String(decrypted, "UTF-8");
    }
}

3. 單例 SqlSessionFactory 管理器

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.ByteArrayInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.InputStream;

public class MyBatisManager {
    private static final String ENCRYPTED_CONFIG = "basic.data"; // 放在 classpath 根目錄
    private static final MyBatisManager INSTANCE = new MyBatisManager();
    private final SqlSessionFactory sqlSessionFactory;

    private MyBatisManager() {
        this.sqlSessionFactory = buildSessionFactory();
    }

    public static MyBatisManager getInstance() {
        return INSTANCE;
    }

    public SqlSessionFactory getSqlSessionFactory() {
        return sqlSessionFactory;
    }

    private SqlSessionFactory buildSessionFactory() {
        InputStream inputStream = null;
        try {
            inputStream = loadDecryptedConfigStream();
            if (inputStream == null) {
                throw new IllegalStateException("無法加載解密後的 MyBatis 配置");
            }
            return new SqlSessionFactoryBuilder().build(inputStream);
        } catch (Exception e) {
            throw new ExceptionInInitializerError("MyBatis 初始化失敗: " + e.getMessage());
        } finally {
            if (inputStream != null) {
                try { inputStream.close(); } catch (Exception ignored) {}
            }
        }
    }

    private InputStream loadDecryptedConfigStream() throws Exception {
        // 獲取 classpath 下加密文件的絕對路徑
        String classPath = MyBatisManager.class.getClassLoader().getResource("").getFile();
        String decodedPath = java.net.URLDecoder.decode(classPath, "UTF-8");
        File encryptedFile = new File(decodedPath + ENCRYPTED_CONFIG);

        if (!encryptedFile.exists()) {
            throw new IllegalStateException("加密配置文件不存在: " + encryptedFile.getAbsolutePath());
        }

        // 讀取加密內容
        StringBuilder sb = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(new FileReader(encryptedFile))) {
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        }

        // 解密並轉為 InputStream
        String decryptedXml = DesUtil.decrypt(sb.toString());
        return new ByteArrayInputStream(decryptedXml.getBytes("UTF-8"));
    }
}

4. 業務代碼中獲取 SqlSession

public class UserDao {
    public User findById(Long id) {
        try (SqlSession session = MyBatisManager.getInstance().getSqlSessionFactory().openSession()) {
            UserMapper mapper = session.getMapper(UserMapper.class);
            return mapper.selectById(id);
        }
    }
}

關鍵技術點解析

技術點說明
單例模式SqlSessionFactory 線程安全、重量級,全應用僅需一個實例,雙重檢查鎖或靜態內部類均可,本文用饿漢式簡化。
類加載器獲取路徑ClassLoader.getResource("") 獲取 classpath 根路徑,配合 URLDecoder.decode 處理空格/中文路徑。
流式解密將解密後的字串轉為 ByteArrayInputStream,避免寫臨時明文文件到磁盤。
異常快速失敗構造器中捕獲異常拋 ExceptionInInitializerError,確保單例初始化失敗時 JVM 能正確報錯。

安全性進階建議

  • 密鑰管理:不要硬編碼密鑰,建議用 Java Keystore、環境變量、配置中心或硬件加密模塊(HSM)管理。
  • 算法升級:DES 已不安全,生產環境請用 AES-256-GCM 或 ChaCha20-Poly1305,並加入完整性校驗(GCM 模式自帶)。
  • 簽名驗證:在加密數據前附加 HMAC 或數字簽名,防止配置文件被竄改。
  • 類加載隔離:若應用支持插件熱插拔,需考慮每個插件獨立的 SqlSessionFactory 及類加載器隔離。

純 Java API 配置備選方案

若完全不想用 XML,可用 org.apache.ibatis.session.Configuration 程式化構建:

DataSource dataSource = new PooledDataSource(driver, url, user, pwd);
Environment env = new Environment("dev", new JdbcTransactionFactory(), dataSource);
Configuration config = new Configuration(env);
config.addMapper(UserMapper.class); // 需映射器接口在 classpath
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(config);

此方式適合配置動態生成、多租戶動態切換數據源等場景,但映射器註解或 XML 仍需編譯期確定。

總結

通過「配置文件加密存儲 + 運行時解密流式加載 + 單例 SqlSessionFactory」組合,可在 C/S 架構中有效保護數據庫憑證,同時保持 MyBatis 原生開發體驗。核心在於不落地明文、單例線程安全、異常快速失敗三原則。生產環境務必升級加密算法與密鑰管理體系,將安全風險降至最低。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved