1

由于作业异步操作,它们无法访问用户的会话。需要找到一个解决方案,以便作业可以访问用户的会话(如果用户当时仍处于登录状态)。

用户会话

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
}
4

1 回答 1

2

异步作业由另一个线程执行(如预期的那样)。会话由应用服务器管理并由请求提供。Spring security 额外管理本地线程中的上下文以在没有请求的情况下访问它(如@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添加或删除数据和/或使用其他实现,如 bounded ArrayBlockingQueue

另一个想法可能是使用执行器框架而不是计划的作业执行。只需在您的请求处理程序中创建一个执行程序并让它为您运行作业:

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() {
    ...
  }
}
于 2013-04-23T06:52:29.097 回答