在MyBatis中使用sequence.NEXTVAL FROM DUAL时如何防范SQL注入?
MyBatis动态序列查询的SQL注入防范方案
问题描述
我在MyBatis中编写SQL查询,需要从后端传入序列名称,当前使用的语句是:
SELECT ${sequenceName}.NEXTVAL FROM DUAL
通常我会用#{}而非${}防范SQL注入,但此处需要字符串拼接,无法通过该方式避免注入。请问是否有解决方案,能在这类动态查询和字符串替换场景下避免SQL注入?我设想的方案如下,是否可行?
SELECT (Select SEQUENCE_NAME from ALL_SEQUENCES where SEQUENCE_NAME = ?).NEXTVAL FROM DUAL
方案分析与解答
你的设想方案不可行。因为子查询返回的是字符串类型的序列名称,数据库无法将字符串直接解析为序列对象,执行时会抛出“无效标识符”类的错误,无法正确调用.NEXTVAL方法。
以下是几种可行的安全方案:
1. Java代码层白名单校验
这是最直接且高效的方式:提前维护系统中所有合法的序列名称列表,在调用MyBatis查询前,先校验传入的sequenceName是否在白名单内,非法名称直接拦截。
// 示例:定义合法序列的白名单(建议配置化管理) private static final Set<String> VALID_SEQUENCE_NAMES = new HashSet<>(Arrays.asList( "SEQ_USER", "SEQ_ORDER", "SEQ_PRODUCT", "SEQ_COMMENT" )); public Long getNextSequenceValue(String sequenceName) { // 统一转为大写,避免大小写问题 String upperSeqName = sequenceName.toUpperCase(); if (!VALID_SEQUENCE_NAMES.contains(upperSeqName)) { throw new IllegalArgumentException("不允许访问的序列名称"); } // 校验通过后调用MyBatis方法 return sequenceMapper.getNextVal(upperSeqName); }
对应的MyBatis SQL仍使用${sequenceName},但因为已经做了严格的白名单校验,完全规避了SQL注入风险。
2. 数据库存储过程校验
创建存储过程,先校验传入的序列名称是否存在(通过查询ALL_SEQUENCES),校验通过后再动态获取序列值,否则抛出异常。以Oracle为例:
CREATE OR REPLACE PROCEDURE GET_SEQ_NEXTVAL( p_seq_name IN VARCHAR2, p_next_val OUT NUMBER ) AS v_seq_exists NUMBER; BEGIN -- 检查序列是否存在 SELECT COUNT(1) INTO v_seq_exists FROM ALL_SEQUENCES WHERE SEQUENCE_NAME = UPPER(p_seq_name); IF v_seq_exists = 0 THEN -- 抛出自定义异常 RAISE_APPLICATION_ERROR(-20001, '序列【' || p_seq_name || '】不存在'); END IF; -- 动态执行获取序列下一个值 EXECUTE IMMEDIATE 'SELECT ' || UPPER(p_seq_name) || '.NEXTVAL FROM DUAL' INTO p_next_val; END;
在MyBatis中调用存储过程时,用#{}传入参数即可,既安全又满足动态序列的需求:
<select id="callGetSeqNextVal" statementType="CALLABLE" resultType="java.lang.Long"> {call GET_SEQ_NEXTVAL(#{sequenceName, mode=IN, jdbcType=VARCHAR}, #{nextVal, mode=OUT, jdbcType=NUMERIC})} </select>
3. MyBatis动态SQL校验(简易版)
如果不想改动Java代码或创建存储过程,也可以在MyBatis的XML映射文件中,用动态SQL做简单的白名单校验,非法参数直接让查询返回空或报错:
<select id="getNextVal" resultType="java.lang.Long"> SELECT ${sequenceName}.NEXTVAL FROM DUAL <where> <!-- 只允许指定的序列名称 --> <if test="sequenceName.toUpperCase() not in ('SEQ_USER', 'SEQ_ORDER', 'SEQ_PRODUCT')"> AND 1 = 0 </if> </where> </select>
不过这种方式如果序列数量较多,维护起来比较繁琐,优先级低于前两种方案。
内容的提问来源于stack exchange,提问作者bobbibrown123
相关产品推荐
相关产品推荐

