Spring Security和@Async(经过身份验证的用户混淆)

时间:2011-03-09 13:24:07

标签: spring asynchronous spring-security

我使用@Async异步调用Spring方法。此方法调用其他使用@PreAuthorize,Spring Security Annotation注释的方法。要使授权工作,我必须将SecurityContextHolder模式设置为MODE_INHERITABLETHREADLOCAL,以便将身份验证信息传递给异步调用。到目前为止一切正常。

但是,当我以其他用户身份退出并登录时,在异步方法中,SecurityContextHolder会存储已注销的旧用户的身份验证信息。它当然会导致不必要的AccessDenied异常。同步调用没有这样的问题。

我已定义<task:executor id="executors" pool-size="10"/>,因此一旦初始化执行程序池中的线程,它不会覆盖身份验证信息,这可能是一个问题吗?

7 个答案:

答案 0 :(得分:34)

我猜MODE_INHERITABLETHREADLOCAL无法正常使用线程池。

作为一种可能的解决方案,您可以尝试子类ThreadPoolTaskExecutor并覆盖其方法以手动传播SecurityContext,然后声明该执行器而不是<task:executor>,如下所示:

public void execute(final Runnable r) {
    final Authentication a = SecurityContextHolder.getContext().getAuthentication();

    super.execute(new Runnable() {
        public void run() {
            try {
                SecurityContext ctx = SecurityContextHolder.createEmptyContext();
                ctx.setAuthentication(a);
                SecurityContextHolder.setContext(ctx);
                r.run();
            } finally {
                SecurityContextHolder.clearContext();
            }
        }
    });
}

答案 1 :(得分:12)

这只是一个需要未来调查的提示(我太累了,但也许有人觉得这对今后的调查很有用):

今天我偶然发现了org.springframework.security.task.DelegatingSecurityContextAsyncTaskExecutor GitHub

看起来他的设计是为了委派安全上下文,以便它已经通过&#34;通过@Async电话。

另请看这篇文章:Spring Security 3.2 M1 Highlights, Servlet 3 API Support听起来很强烈。

答案 2 :(得分:6)

使用Ralph和Oak的信息 -

如果您想让@Async使用标准任务执行程序标记,您可以像这样设置Spring XML配置

<task:annotation-driven executor="_importPool"/>
<task:executor id="_importPool" pool-size="5"/>

<bean id="importPool"
          class="org.springframework.security.task.DelegatingSecurityContextAsyncTaskExecutor">
     <constructor-arg ref="_importPool"/>
</bean>

然后在@Async方法中,指定要使用的池

@Async("importPool")
public void run(ImportJob import) {
   ...
}

这应该有效,所以当你调用@Async方法时,线程池线程将使用与调用线程相同的安全上下文

答案 3 :(得分:5)

我也遇到了这个问题。使用DelegatingSecurityContextAsyncTaskExecutor正确配置ThreadPoolTask​​Executor非常重要。调用initialize()方法也很重要,否则会引发错误。

// define the TaskExecutor as a bean
@Bean("threadPoolTaskExecutor")
public TaskExecutor getAsyncExecutor() {

  ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
  executor.setCorePoolSize(20);
  executor.setMaxPoolSize(1000);
  executor.setWaitForTasksToCompleteOnShutdown(true);
  executor.setThreadNamePrefix("Async-");
  executor.initialize(); // this is important, otherwise an error is thrown
  return new DelegatingSecurityContextAsyncTaskExecutor(executor); // use this special TaskExecuter
}

// the method in your business logic which is called async
@Override
@Async("threadPoolTaskExecutor")
public void yourLogic() {
  [..]
}

答案 4 :(得分:1)

根据@Ralph回答,$(".all").click(function(event) { event.preventDefault(); $.post( $(this).parent().attr("action"), $(this).parent().find(':input').serializeArray(), function(info){ $("#result").html(info); } ); }); Aync event Springthreadpooling一致,并使用http://docs.spring.io/autorepo/docs/spring-security/4.0.0.M1/apidocs/org/springframework/security/task/DelegatingSecurityContextAsyncTaskExecutor.html

委托安全性

示例代码

<bean id="applicationEventMulticaster"
    class="org.springframework.context.event.SimpleApplicationEventMulticaster">
    <property name="taskExecutor">
        <ref bean="delegateSecurityAsyncThreadPool"/>
    </property>
</bean>

<bean id="threadsPool"
    class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
</bean>


<bean id="delegateSecurityAsyncThreadPool"
    class="org.springframework.security.task.DelegatingSecurityContextTaskExecutor">
    <constructor-arg ref="threadsPool"/>
</bean>

答案 5 :(得分:0)

Jus要添加@axtavt的答案,你还想覆盖其他方法。

@Override
    public <T> Future<T> submit(Callable<T> task) {
        ExecutorService executor = getThreadPoolExecutor();
        final Authentication a = SecurityContextHolder.getContext().getAuthentication();
        try {
            return executor.submit(new Callable<T>() {
                @Override
                public T call() throws Exception {
                    try {
                        SecurityContext ctx = SecurityContextHolder.createEmptyContext();
                        ctx.setAuthentication(a);
                        SecurityContextHolder.setContext(ctx);
                        return task.call();
                    } catch (Exception e) {
                        slf4jLogger.error("error invoking async thread. error details : {}", e);
                        return null;
                    } finally {
                        SecurityContextHolder.clearContext();
                    }
                }
            });
        } catch (RejectedExecutionException ex) {
            throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
        }
    }

答案 6 :(得分:0)

正如已经提到的,对于池化线程环境应该使用 DelegatingSecurityContextAsyncTaskExecutor,而不是 MODE_INHERITABLETHREADLOCAL(阅读 here)。

为 Spring Boot 项目留下简单的 DelegatingSecurityContextAsyncTaskExecutor 配置,它将简单地使用默认的 Spring Boot 池来执行异步任务:

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
    private final ThreadPoolTaskExecutor defaultSpringBootAsyncExecutor;

    public AsyncConfig(ThreadPoolTaskExecutor defaultSpringBootAsyncExecutor) {
        this.defaultSpringBootAsyncExecutor = defaultSpringBootAsyncExecutor;
    }

    @Override
    public Executor getAsyncExecutor() {
        return new DelegatingSecurityContextAsyncTaskExecutor(defaultSpringBootAsyncExecutor);
    }
}