jeecgboot/JeecgBoot · error · IllegalArgumentException

非法的任务类名:${classname},仅允许 org.jeecg 包下的Job类

Error message

非法的任务类名:${classname},仅允许 org.jeecg 包下的Job类

What it means

A security guard in QuartzJobServiceImpl.getClass() that blocks arbitrary class instantiation (the classic Quartz RCE vector). Before any reflection, the classname is rejected unless it starts with the literal prefix "org.jeecg.". This is the first line of defense against scheduling a deserialization/gadget class.

Source

Thrown at jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/quartz/service/impl/QuartzJobServiceImpl.java:183

	 */
	private void schedulerDelete(String id) {
		try {
			scheduler.pauseTrigger(TriggerKey.triggerKey(id));
			scheduler.unscheduleJob(TriggerKey.triggerKey(id));
			scheduler.deleteJob(JobKey.jobKey(id));
		} catch (Exception e) {
			log.error(e.getMessage(), e);
			throw new JeecgBootException("删除定时任务失败");
		}
	}

	/**
	 * 安全加载Job类:仅允许 org.jeecg. 包下的类,且必须实现 org.quartz.Job 接口
	 */
	private static Job getClass(String classname) throws Exception {
		// 包名白名单校验,防止任意类实例化导致RCE
		if (classname == null || !classname.startsWith("org.jeecg.")) {
			throw new IllegalArgumentException("非法的任务类名:" + classname + ",仅允许 org.jeecg 包下的Job类");
		}
		//update-begin---author:scott ---date:20260416  for:【PR#9538】Class.forName使用上下文类加载器,增强部署兼容性-----------
		Class<?> clazz = Class.forName(classname, true, Thread.currentThread().getContextClassLoader());
		//update-end---author:scott ---date:20260416  for:【PR#9538】Class.forName使用上下文类加载器,增强部署兼容性-----------
		// 校验是否实现了 org.quartz.Job 接口
		if (!Job.class.isAssignableFrom(clazz)) {
			throw new IllegalArgumentException("非法的任务类:" + classname + ",必须实现 org.quartz.Job 接口");
		}
		return (Job) clazz.getDeclaredConstructor().newInstance();
	}

}

View on GitHub (pinned to 96fb33f5ec)

Solutions

  1. Move or wrap the job class under an org.jeecg.* package so it passes the whitelist.
  2. If you legitimately require another package, extend the whitelist check in getClass() and review the RCE exposure first.
  3. Ensure jobClassName is non-null and correctly prefixed before persisting it.

Example fix

// before: third-party job
package com.acme.jobs;
public class AcmeJob implements Job { ... }
// after: place under the whitelisted package (or delegate)
package org.jeecg.modules.acme.job;
public class AcmeJob implements Job { ... }
Defensive patterns

Strategy: validation

Validate before calling

// Reject non-whitelisted class names before they reach the scheduler.
private static final String JOB_PREFIX = "org.jeecg.";
if (jobClassName == null || !jobClassName.startsWith(JOB_PREFIX)) {
    throw new IllegalArgumentException("非法的任务类名: " + jobClassName);
}

Type guard

public static boolean isWhitelistedJobClassName(String name) {
    return name != null && name.startsWith("org.jeecg.");
}

Try / catch

try {
    quartzJobService.schedulerAdd(job);
} catch (JeecgBootException e) {
    if (e.getMessage() != null && e.getMessage().contains("仅允许 org.jeecg")) {
        return Result.error("任务类必须在 org.jeecg 包下");
    }
    throw e;
}

Prevention

When it happens

Trigger: Submitting a jobClassName that does not start with "org.jeecg." — e.g. an ysoserial gadget like "org.apache.commons.collections.functors...", a third-party class "com.example.MyJob", or a null value (the null check also routes here).

Common situations: Legitimate job classes placed outside the jeecg package; copy-paste from a tutorial using a different package; adversarial/admin input attempting to trigger a gadget chain; null jobClassName from a malformed request.

Related errors


AI-assisted analysis of jeecgboot/JeecgBoot@96fb33f5ec (2026-08-14). Data as JSON: /api/errors/1ca5bc63c7797f49. Report an issue: GitHub.