You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用JSQLParser的ExpressionDeParser实现MySQL字段加解密拦截?

刚好之前做过类似的需求,用JSQLParser来实现SQL的加密解密拦截完全可行,核心是通过自定义AST访问器(Visitor)来识别并修改特定的SQL节点。下面给你一步步拆解实现方案:

核心思路

JSQLParser会把SQL解析成抽象语法树(AST),我们只需要遍历这个树,找到目标字段对应的节点:

  • 对于INSERT/UPDATE,把目标字段对应的JDBC参数(?)替换成AES_ENCRYPT(?, 密钥)
  • 对于SELECT,把目标字段替换成AES_DECRYPT(字段名, 密钥) AS 原字段名,保证上层业务拿到的字段名不变
一、处理INSERT/UPDATE语句(加密参数)

我们需要自定义StatementDeParser的子类,重写visit(Insert)和visit(Update)方法,定位到目标字段对应的参数并替换:

  • INSERT中:先找到目标字段在列列表中的索引,再找到VALUES中对应位置的?,替换为加密函数
  • UPDATE中:遍历SET子句的键值对,找到目标字段对应的赋值参数,替换为加密函数
二、处理SELECT语句(解密字段)

同样在自定义Visitor中重写visit(Select)方法,通过SelectDeParser遍历SELECT的列项,把目标字段替换为解密函数,并保留原字段别名。

三、完整可运行示例代码
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.Function;
import net.sf.jsqlparser.expression.JdbcParameter;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectExpressionItem;
import net.sf.jsqlparser.statement.update.Update;
import net.sf.jsqlparser.util.deparser.SelectDeParser;
import net.sf.jsqlparser.util.deparser.StatementDeParser;

import java.util.List;

public class SqlEncryptDecryptHandler extends StatementDeParser {
    // 要加密/解密的目标字段
    private static final String TARGET_COLUMN = "name";
    // AES加密密钥(建议从配置中心读取,不要硬编码)
    private static final String AES_SECRET_KEY = "your_secure_key_here";

    public SqlEncryptDecryptHandler() {
        super(new net.sf.jsqlparser.util.deparser.ExpressionDeParser(), new StringBuffer());
    }

    // 处理INSERT语句
    @Override
    public void visit(Insert insert) {
        List<String> columns = insert.getColumns();
        if (columns != null && columns.contains(TARGET_COLUMN)) {
            int targetIndex = columns.indexOf(TARGET_COLUMN);
            // 遍历所有VALUES组(支持批量插入)
            for (List<Expression> valueGroup : insert.getValues().getExpressions()) {
                if (targetIndex < valueGroup.size()) {
                    Expression param = valueGroup.get(targetIndex);
                    if (param instanceof JdbcParameter) {
                        // 构建AES_ENCRYPT函数
                        Function aesEncrypt = new Function();
                        aesEncrypt.setName("AES_ENCRYPT");
                        aesEncrypt.addParameter(param);
                        aesEncrypt.addParameter(buildKeyExpression());
                        valueGroup.set(targetIndex, aesEncrypt);
                    }
                }
            }
        }
        super.visit(insert);
    }

    // 处理UPDATE语句
    @Override
    public void visit(Update update) {
        List<Expression> setItems = update.getSetExpressions();
        // SET子句是[列, 值, 列, 值...]的结构,步长为2遍历
        for (int i = 0; i < setItems.size(); i += 2) {
            Expression columnExpr = setItems.get(i);
            String columnName = columnExpr.toString();
            if (TARGET_COLUMN.equals(columnName)) {
                Expression valueExpr = setItems.get(i + 1);
                if (valueExpr instanceof JdbcParameter) {
                    Function aesEncrypt = new Function();
                    aesEncrypt.setName("AES_ENCRYPT");
                    aesEncrypt.addParameter(valueExpr);
                    aesEncrypt.addParameter(buildKeyExpression());
                    setItems.set(i + 1, aesEncrypt);
                }
            }
        }
        super.visit(update);
    }

    // 处理SELECT语句
    @Override
    public void visit(Select select) {
        SelectDeParser selectDeParser = new SelectDeParser(getExpressionVisitor(), getBuffer()) {
            @Override
            public void visit(SelectExpressionItem item) {
                Expression expr = item.getExpression();
                String columnName = expr.toString();
                if (TARGET_COLUMN.equals(columnName)) {
                    // 构建AES_DECRYPT函数,保留原别名
                    Function aesDecrypt = new Function();
                    aesDecrypt.setName("AES_DECRYPT");
                    aesDecrypt.addParameter(expr);
                    aesDecrypt.addParameter(buildKeyExpression());
                    item.setExpression(aesDecrypt);
                    if (item.getAlias() == null) {
                        item.setAlias(TARGET_COLUMN);
                    }
                }
                super.visit(item);
            }
        };
        select.getSelectBody().accept(selectDeParser);
    }

    // 构建密钥表达式(避免重复代码)
    private Expression buildKeyExpression() {
        try {
            return CCJSqlParserUtil.parseExpression("'" + AES_SECRET_KEY + "'");
        } catch (JSQLParserException e) {
            throw new RuntimeException("Failed to build AES key expression", e);
        }
    }

    // 对外暴露的SQL处理方法
    public static String processSql(String originalSql) throws JSQLParserException {
        Statement statement = CCJSqlParserUtil.parse(originalSql);
        SqlEncryptDecryptHandler handler = new SqlEncryptDecryptHandler();
        statement.accept(handler);
        return handler.getBuffer().toString();
    }

    // 测试用例
    public static void main(String[] args) throws JSQLParserException {
        // 测试INSERT
        String insertSql = "insert into customer (id, name) values (?,?)";
        System.out.println("原INSERT: " + insertSql);
        System.out.println("处理后INSERT: " + processSql(insertSql));
        // 输出: insert into customer (id, name) values (?, AES_ENCRYPT(?, 'your_secure_key_here'))

        // 测试UPDATE
        String updateSql = "update customer set name = ? where id = ?";
        System.out.println("\n原UPDATE: " + updateSql);
        System.out.println("处理后UPDATE: " + processSql(updateSql));
        // 输出: update customer set name = AES_ENCRYPT(?, 'your_secure_key_here') where id = ?

        // 测试SELECT
        String selectSql = "SELECT id, name FROM customer where id = ?";
        System.out.println("\n原SELECT: " + selectSql);
        System.out.println("处理后SELECT: " + processSql(selectSql));
        // 输出: SELECT id, AES_DECRYPT(name, 'your_secure_key_here') AS name FROM customer where id = ?
    }
}
注意事项
  • 密钥安全:绝对不要硬编码密钥,建议从配置中心、环境变量或密钥管理服务读取
  • 复杂场景兼容:如果你的SQL包含表别名(比如SELECT c.name FROM customer c)、子查询、批量插入多组值等,需要扩展代码中的字段匹配逻辑,比如解析表别名后再判断字段名
  • 性能优化:AST解析有一定开销,建议只针对需要加密的表和字段做处理,避免全量SQL解析
  • 异常处理:代码中可以添加更多的空值判断和异常捕获,避免解析异常导致业务中断

内容的提问来源于stack exchange,提问作者Ardile

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 06:45:10