Spring安全性:如何在异步作业中访问经过身份验证的用户

时间:2013-04-23 06:37:46

标签: java spring spring-security

由于作业以异步方式运行,因此无法访问用户的会话。需要找到一个解决方案,以便作业可以访问用户的会话(如果用户此时仍然登录)。

用户会话

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
}

1 个答案:

答案 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() {
    ...
  }
}