EJB防SQL注入核心是禁用手动拼SQL,必须通过JPA参数化查询(如命名参数+setParameter)或PreparedStatement绑定用户输入,禁用字符串拼接、${}、动态JPQL及未校验的原生SQL。

在 EJB 中防 SQL 注入,核心不是“怎么写 SQL”,而是“根本别手动拼 SQL”——EJB 容器本身不提供 SQL 构建能力,所有数据库交互必须委托给 JPA、JDBC 或其他持久层,而安全边界就在这些层的调用方式里。
用 EntityManager 的参数化查询(JPA)是默认安全路径
JPA 规范强制要求 EntityManager.createQuery() 和 createNamedQuery() 支持命名参数或位置参数,只要不用字符串拼接,就不会有注入风险。
- ✅ 正确:使用
:param命名参数,值通过setParameter()绑定 - ❌ 错误:用
+拼接用户输入到 JPQL 字符串中(JPQL 不支持动态表名/列名,拼接即高危) - ⚠️ 注意:
createNativeQuery()是例外——它执行原生 SQL,若传入的 SQL 字符串含用户输入,依然会注入 - 示例:
em.createQuery("SELECT u FROM User u WHERE u.email = :email").setParameter("email", inputEmail)
PreparedStatement 在 EJB 中仍需手动管理连接
EJB 本身不屏蔽 JDBC,但你不能直接 new Connection;必须通过 @Resource DataSource 获取连接。此时安全责任回到开发者手上:
- 必须用
connection.prepareStatement(sql),而非createStatement() - 所有用户数据必须走
setString()、setLong()等绑定方法,禁止String.format()或concat() - 不要在 EJB 方法里 try-with-resources 外部关闭
DataSource获取的连接(容器可能复用),但PreparedStatement和ResultSet必须显式 close - 错误示范:
"SELECT * FROM users WHERE name = '" + userName + "'"—— 这行代码哪怕在@Statelessbean 里也一样危险
别碰 ${} 和动态 JPQL/HQL
某些旧项目会用字符串模板生成 JPQL,比如基于用户选择的字段排序:"ORDER BY " + userInputSortField。这不可接受:
- JPA 不允许参数化表名、列名、排序方向(
ASC/DESC)——这些属于 SQL 结构,不是数据 - 解决方案只有白名单校验:
if (!List.of("name", "email", "created_at").contains(sortField)) throw new IllegalArgumentException(); - MyBatis 用户注意:
${}是字符串替换,#{}才是预编译参数——EJB 中若集成 MyBatis,必须禁用所有${}的使用场景 - Hibernate 的
Criteria API是更安全的替代方案,它全程构建类型安全的对象树,无字符串拼接风险
权限与配置比代码更关键
即使某处漏掉参数化,低权限数据库账户也能卡住大部分攻击出口:
- EJB 应用使用的数据库账号,必须由 DBA 单独创建,只授予
SELECT/INSERT/UPDATE到具体业务表,严禁DROP、DELETE、FILE、LOAD DATA INFILE - MySQL 需确认
local_infile=OFF,PostgreSQL 需禁用pg_read_file()等高危函数 - 不要在 EJB 的
persistence.xml或 Spring Boot 的application.properties中开启show_sql=true或format_sql=true生产环境,避免日志泄露语句结构 - 若用 WildFly/JBoss,检查
security-domain是否绑定到最小权限数据源,而非共享 root 数据源
真正容易被忽略的点是:EJB 容器不会替你审核 JPQL 字符串内容,也不会拦截 createNativeQuery() 的 SQL 拼接。安全不在“EJB 是否支持防注入”,而在你是否把所有 SQL 构建行为锁死在参数化通道内——连一个 StringBuilder.append() 拼接字段名,都可能成为盲注入口。

















