由于作业以异步方式运行,因此无法访问用户的会话。需要找到一个解决方案,以便作业可以访问用户的会话(如果用户此时仍然登录)。
用户会话
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
public class UserPrincipal implements UserDetails {
private User user;
private Collection<SimpleGrantedAuthority> grantedAuthorities;
public UserPrincipal(User user) {
Assert.notNull(user);
this.user = user;
Set<SimpleGrantedAuthority> authorities = new LinkedHashSet<>();
for (Role role : user.getRoles()) {
authorities.add(new SimpleGrantedAuthority(role.getName().toUpperCase(Locale.ENGLISH)));
}
grantedAuthorities = Collections.unmodifiableCollection(authorities);
}
}
抽象工作类
public abstract class Job implements Runnable {
protected Logger logger = LoggerFactory.getLogger(getClass());
protected Job() {
}
@Override
public final void run() {
logger.debug("starting work");
/* code goes here */
logger.debug("work is done");
}
}
工作类
@Component
@Scope(value = "prototype")
public class ProcessLoggingJob extends Job {
@Override
protected void work(Map<String, Object> context) throws Exception {
// need to access user session here
}
答案 0 :(得分:2)
异步作业由另一个线程执行(如预期的那样)。会话由应用程序服务器管理,并由请求提供。 Spring安全附加管理本地线程中的上下文,以便在没有请求的情况下访问它(正如@Michael在他的答案中所示)。
由于安全上下文(从会话中获取)保存在一个本地线程(通常是应用程序服务器的HTTP线程)中,异步作业在另一个线程中运行,没有机会可以访问请求线程的本地线程。
我看到的唯一机会是使用队列机制,从请求线程创建新的作业数据,包括用户数据,将它们传递到队列,处理作业中队列中的数据。
在请求处理线程中,这可能看起来像(缺少空值处理):
private BlockingQueue<UserDetails> usersToProceedAsync;
public void doSomethingInRequestThread() throws InterruptedException {
UserDetails principal = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()
...
usersToProceedAsync.put(principal);
}
工作实施可能是:
private BlockingQueue<UserDetails> usersToProceedAsync;
protected void work(Map<String, Object> context) throws Exception {
UserDetails principal = usersToProceedAsync.poll();
if (principal != null) {
...
}
}
您只需通过向两者注入相同的队列来连接这两个类(例如LinkedBlockingQueue
)。如果每个作业运行创建了作业实例,则需要一个工厂来注入队列。
每次请求创建一个作业数据时要小心!如何确保异步作业足够快以处理所有工作?您可以使用其他方法调整行为,以便添加或删除BlockingQueue中的数据和/或使用其他实现(例如有界ArrayBlockingQueue
)来调整行为。
另一个想法可能是使用executor框架而不是预定的作业执行。只需在请求处理程序中创建一个执行程序,让它为您运行作业:
private final Executor asyncJobs = Executors.newCachedThreadPool()
public void doSomethingInRequestThread() throws InterruptedException {
UserDetails principal = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()
...
asyncJobs.execute(new AsyncJob(principal));
}
private class AsyncJob implements Runnable {
private final UserDetails principal;
public AsyncJob(UserDetails principal) {
this.principal = principal;
}
public void run() {
...
}
}