如何避免 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_;
当提交的第一个可运行对象是注入 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_;