Xxl-job使用注意事项

2,255 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第8天,点击查看活动详情

accessToken的漏洞

Xxl-Job使用版本 2.2.0 关于accessToken 顾名思义是一种使用凭证,为了安全着想的.具体使用场景是在执行器与admin进行通信是使用:

  1. 执行器注册
  2. 执行器验证admin的请求是否安全 关于第二点是在admin向执行器发送请求在请求头中进行设置的.
public static final String XXL_JOB_ACCESS_TOKEN = "XXL-JOB-ACCESS-TOKEN";

而执行器收到请求后会对accessToken进行比对:

if (accessToken!=null
        && accessToken.trim().length()>0
        && !accessToken.equals(accessTokenReq)) {
    return new ReturnT<String>(ReturnT.FAIL_CODE, "The access token is wrong.");
}
accessToken漏洞分析

XXL-JOB为了灵活支持多语言以及脚本任务,提供了创新的 “GLUE模式”,该模式任务特点如下:

  • 多语言支持:支持Java、Shell、Python、NodeJS、PHP、PowerShell……等类型。
  • Web IDE:任务以源码方式维护在调度中心,支持通过Web IDE在线开发、维护。
  • 动态生效:用户在线通过Web IDE开发的任务代码,远程推送至执行器,实时加载执行。

image.png 如上图所示,如果在 GLUE 模式任务代码中写入攻击代码,推送到执行器执行即可造成远程攻击。(问题点)

由于 XXL-JOB 官方版本原生自带鉴权组件,开启后可保障系统底层通讯安全。XXL-JOB 作者表示正常情况下调度中心与执行器底层通讯是安全的,不存在远程命令漏洞。但如果执行器未开启访问令牌,会导致无法识别并拦截非法的调度请求。恶意请求方可以借助 GLUE 模式,推送恶意攻击代码实现远程攻击。如下图所示:

image.png

解决方式: 开启 XXL-JOB 自带的鉴权组件 执行器.admin君配置 “xxl.job.accessToken”即可

XxlJob注解使用不当

当配置XxlJob注解对应的定时任务时,

/**
 * 1、简单任务示例(Bean模式)
 */
@XxlJob("demoJobHandler")
public ReturnT<String> demoJobHandler() throws Exception {
    XxlJobLogger.log("XXL-JOB, Hello World.");

    for (int i = 0; i < 5; i++) {
        XxlJobLogger.log("beat at:" + i);
        TimeUnit.SECONDS.sleep(2);
    }

   return ReturnT.SUCCESS;
}

启动对应的执行器时发现应用直接被shutdown.

2022-08-10 14:05:23.712 ERROR pid:23584 [main] c.x.job.core.executor.XxlJobExecutor.stopEmbedServer[160] - null java.lang.NullPointerException: null at com.xxl.job.core.executor.XxlJobExecutor.stopEmbedServer(XxlJobExecutor.java:158) at com.xxl.job.core.executor.XxlJobExecutor.destroy(XxlJobExecutor.java:85) at com.xxl.job.core.executor.impl.XxlJobSpringExecutor.destroy(XxlJobSpringExecutor.java:55) at org.springframework.beans.factory.support.DisposableBeanAdapter.destroy(DisposableBeanAdapter.java:258) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroyBean(DefaultSingletonBeanRegistry.java:579) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroySingleton(DefaultSingletonBeanRegistry.java:551) at org.springframework.beans.factory.support.DefaultListableBeanFactory.destroySingleton(DefaultListableBeanFactory.java:1056) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroySingletons(DefaultSingletonBeanRegistry.java:512) at org.springframework.beans.factory.support.DefaultListableBeanFactory.destroySingletons(DefaultListableBeanFactory.java:1049) at org.springframework.context.support.AbstractApplicationContext.destroyBeans(AbstractApplicationContext.java:1057) at org.springframework.context.support.AbstractApplicationContext.doClose(AbstractApplicationContext.java:1026) at org.springframework.context.support.AbstractApplicationContext.close(AbstractApplicationContext.java:975) at org.springframework.boot.SpringApplication.handleRunFailure(SpringApplication.java:800) at org.springframework.boot.SpringApplication.run(SpringApplication.java:322) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1215) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1204) at com.stnts.weplay.OrderStarter.main(OrderStarter.java:20) 2022-08-10 14:05:23.716 INFO pid:23584 [main] org.mongodb.driver.connection.info[71] - Closed connection [connectionId{localValue:4, serverValue:102228}] to 10.0.41.172:27018 because the pool has been closed. 2022-08-10 14:05:23.720 INFO pid:23584 [main] c.alibaba.druid.pool.DruidDataSource.close[2003] - {dataSource-1} closing ... 2022-08-10 14:05:23.723 INFO pid:23584 [main] c.alibaba.druid.pool.DruidDataSource.close[2075] - {dataSource-1} closed 2022-08-10 14:05:23.836 INFO pid:23584 [main] o.s.s.c.ThreadPoolTaskExecutor.shutdown[208] - Shutting down ExecutorService 探究执行器启动流程时发现: 启动入口: XxlJobSpringExecutor#afterSingletonsInstantiated 初始化XxlJob注解的定时任务时: 若方法未定义参数,则抛出RuntimeException异常导致应用停止

  for (Map.Entry<Method, XxlJob> methodXxlJobEntry : annotatedMethods.entrySet()) {
            Method method = methodXxlJobEntry.getKey();
            XxlJob xxlJob = methodXxlJobEntry.getValue();
            if (xxlJob == null) {
                continue;
            }

            String name = xxlJob.value();
            if (name.trim().length() == 0) {
                throw new RuntimeException("xxl-job method-jobhandler name invalid, for[" + bean.getClass() + "#" + method.getName() + "] .");
            }
            if (loadJobHandler(name) != null) {
                throw new RuntimeException("xxl-job jobhandler[" + name + "] naming conflicts.");
            }

            // execute method
            if (!(method.getParameterTypes().length == 1 && method.getParameterTypes()[0].isAssignableFrom(String.class))) {
                throw new RuntimeException("xxl-job method-jobhandler param-classtype invalid, for[" + bean.getClass() + "#" + method.getName() + "] , " +
                        "The correct method format like " public ReturnT<String> execute(String param) " .");
            }
            if (!method.getReturnType().isAssignableFrom(ReturnT.class)) {
                throw new RuntimeException("xxl-job method-jobhandler return-classtype invalid, for[" + bean.getClass() + "#" + method.getName() + "] , " +
                        "The correct method format like " public ReturnT<String> execute(String param) " .");
            }
            method.setAccessible(true);

            // init and destory
            Method initMethod = null;
            Method destroyMethod = null;

            if (xxlJob.init().trim().length() > 0) {
                try {
                    initMethod = bean.getClass().getDeclaredMethod(xxlJob.init());
                    initMethod.setAccessible(true);
                } catch (NoSuchMethodException e) {
                    throw new RuntimeException("xxl-job method-jobhandler initMethod invalid, for[" + bean.getClass() + "#" + method.getName() + "] .");
                }
            }
            if (xxlJob.destroy().trim().length() > 0) {
                try {
                    destroyMethod = bean.getClass().getDeclaredMethod(xxlJob.destroy());
                    destroyMethod.setAccessible(true);
                } catch (NoSuchMethodException e) {
                    throw new RuntimeException("xxl-job method-jobhandler destroyMethod invalid, for[" + bean.getClass() + "#" + method.getName() + "] .");
                }
            }

            // registry jobhandler
            registJobHandler(name, new MethodJobHandler(bean, method, initMethod, destroyMethod));
        }
    }

}

综上所述,任务对应方法配置需要满足以下几点:

  1. 方法形参只能有一个,且必须是String类型及其子类型
  2. 方法返回值必须是ReturnT及其子类型.
  3. xxlJob注解标注的init和destroy方法必须与该注解所对应方法在同一个类中,且必须无参.

定时任务过多导致线程数疯涨.

由于针对每个XxlJob注解标注的定时任务都会对应一个单独的线程来执行该任务,那么一旦任务过多,且执行频繁,路由策略如果都选择再同一个节点和是哪个执行,那么势必导致该节点线程数过多,上下文切换频繁,导致cpu利用率不高,最终导致性能下降.