如何避免 ExecutorService 覆盖 Runnable 的安全主体

How to avoid ExecutorService from overridding Security Principal of a Runnable

当提交的第一个可运行对象是注入 ExecutorService 时,已为该可运行对象正确设置安全主体。每个随后提交的可运行对象都将获得原始用户的安全主体,而不是保留当前可运行对象。我的开发机器是 运行 Wildfly 8.2 .

我正在为异步处理创建一个报告系统。我创建了一个服务来检查哪个用户创建了任务并确保只有该用户才能启动或完成任务。该服务的代码如下。

@Stateless
public class ReportingService {
   //EE injection security context
   @Resource
   SessionContext context;
   //CDI security Principal
   @Inject
   Principal principal;

   //this method handles getting the username for EE injection or CDI
   private String getCurrentUser() {
       if (context != null) {
           return context.getCallerPrincipal().getName();
       }
       if (principal != null) {
           return principal.getName();
       }
       return null;
   }

   @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
   @Transactional
   public void registerTask(String taskId) {
       //Create task
       //set task.submittedBy = getCurrentUser()
       //persist task
       //code has been omitted since it is working
   }

   private void validateCurrentUserRegisteredJob(String taskId) {
       String user = //get user that created task with id = id from DB
       String currentUser = getCurrentUser();
       if (!user.equals(currentUser)) {
           throw new EJBAccesException("Current user "+currentUser+" did not register task");
       }
   }

   @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
   @Transactional
   public void startTask(String taskId) {
       validateCurrentUserRegisteredJob(taskid);
       //retrieve task entity, set start time to now, and merge
   }
   ...
}

下面是我的可运行代码

public TaskRunner() implements Runnable {
    //CDI principal
    @Inject
    Principal principal;
    @Inject
    ReportingService rs;

    private taskId;

    public void setTaskId() {...}

    public void run() {
       log.debug("Inside Runner Current User: "+principal.getName());
       rs.startTask(taskId);
       ....
    }
}

以下是由启动进程的 REST 端点调用的无状态 Bean 的代码

@Stateless
public ProjectService() {
    @Inject
    Instance<TaskRunner> taskRunner;
    @Inject
    ReportingService reportingService;

    //ExecutorService that is create from Adam Bien's porcupine project
    @Inject
    @Dedicated
    ExecutorService es;

    //method that is called by rest enpoint to kick off 
    public void performAsynchAction(List<String> taskIds, ...rest of args...) {
        taskIds.stream().forEach(t -> {
            //registers task with user that made REST call
            reportingService.registerTask(t);
            TaskRunner runner = taskRunner.get();
            runner.setTaskId(t);
            log.debug("Created runner. Principal: "+runner.principal.getName());
            es.submit(runner);
        });
    }
}

这是调用流程的图表

REST -> ProjectService.performAsynchAction(...)
         -> reportingService.registerTask(...)
         -> create CDI injected Runnable
         -> submit runner to executor service
             -> ExecutorService calls Runner.run()
                 -> rs.startTask(taskId)

我第一次调用Rest端点为user1,注册任务:1-2。它们都按预期工作,我在日志中得到以下输出。

Created runner. Principal: user1
Created runner. Principal: user1
Inside Runner Current User: user1
Inside Runner Current User: user1

下次我与 user2 进行相同的 REST 调用时,我在日志中得到以下输出

Created runner. Principal: user2
Inside Runner Current User: user1
EJBAccessException Current user user1 did not register task

第一次将 Runnable 提交给 ExecutorService 时,似乎正确设置了 Runnable 的安全主体。但是对于提交给 ExecutorService 的每个后续 Runneable 使用第一个提交的 runnable 的安全主体。这是错误还是预期的行为?有人知道潜在的解决方法吗?

编辑:我发现我用来创建 ExecutorService 的 porcupine 项目没有被容器管理。一旦我切换到 ManagedExecutorService,SessionContext 就会被正确传播。

@Resource(lookup = "java:jboss/ee/concurrency/executor/customExecutor")
private ManagedExecutorService es;

我认为问题是,你 @Inject 一个 @Dependent 作用域 ExecutorService 变成了一个 @Stateless bean。
@Stateless bean 可以合并和重用,而 @Dependent CDI bean 是通过引用存储的,因此在重用 @Stateless bean 时不会重新创建。

在不知道您的 ExecutorService 实现的情况下,我猜想它会在第一个 运行 期间创建上下文线程,并在第二个 运行 中重新使用它们,而不调整上下文。

您可以 "force" 您的 ProjectService 通过将其封装到 @RequestScoped bean 中来创建新的 ExecutorService:

@RequestScoped
public class ESHolder {
    @Inject
    @Dedicated
    ExecutorService eS;

    public ExecutorService getES() {
        return eS;
    }
}


@Stateless
public ProjectService() {
    // ...

    @Inject        
    ESHolder esHolder;

    public void performAsynchAction(List<String> taskIds, ...rest of args...) {
        taskIds.stream().forEach(t -> {
            // ...

            esHolder.getES().submit(runner); 
        });
    }
}

我想通了。我查看了 porcupine 代码,发现 ExecutorService 并未由 Container 管理。我创建了一个 ManagerExecutorService,然后正确传播了 SessionContext。

@Resource(lookup = "java:jboss/ee/concurrency/executor/customExecutor")
private ManagedExecutorService es_;