获取运行异步方法的用户

时间:2016-05-13 15:52:44

标签: java spring asynchronous spring-security

我试图在应用程序弹簧中从弹簧上下文中获取用户,如下所示:

Authentication auth = SecurityContextHolder.getContext().getAuthentication();

问题是这些方法是异步的,带有注释@Async:

@Service
@Transactional
public class FooServiceImpl implements FooService {

    @Async("asyncExecutor")
    public void fooMethod(String bar) {
        System.out.println("Foo: " + bar);
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    }
}

问题是异步方法在另一个上下文中的另一个线程中运行。 我尝试过使用SecurityContextDelegationAsyncTaskExecutor。用户传播到异步方法但是如果我注销,则异步方法中的用户为null。这是我的代码:

@Configuration
@EnableAsync
public class SpringAsyncConfig implements AsyncConfigurer {

    @Override
    @Bean(name = "asyncExecutor")
    public Executor getAsyncExecutor() {

        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

        executor.setMaxPoolSize(1);
        executor.setThreadGroupName("MyCustomExecutor");
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setBeanName("asyncExecutor");
        executor.initialize();

        return new DelegatingSecurityContextAsyncTaskExecutor(executor);
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new CustomAsyncExceptionHandler();
    }
}

我还使用了ThreadPoolTask​​Executor并使用" MODE_INHERITABLETHREADLOCAL"设置spring security的上下文。但结果是一样的。如果我登录到应用程序,则用户不为null。如果我没有记录,则用户为空。我真的想要运行该方法的用户,而不是当前用户记录的用户。我的代码:

@Configuration
@EnableAsync
public class SpringAsyncConfig implements AsyncConfigurer {

    @Override
    @Bean(name = "asyncExecutor")
    public Executor getAsyncExecutor() {

        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

        executor.setMaxPoolSize(1);
        executor.setThreadGroupName("MyCustomExecutor");
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setBeanName("asyncExecutor");
        executor.initialize();

        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new CustomAsyncExceptionHandler();
    }

}

@Configuration
@ComponentScan(basePackageClasses = Application.class, includeFilters = @Filter({Controller.class}), useDefaultFilters = true)
public class MvcConfiguration extends WebMvcConfigurationSupport {

    //others beans

    @Bean
    public MethodInvokingFactoryBean methodInvokingFactoryBean() {
        MethodInvokingFactoryBean methodInvokingFactoryBean = new MethodInvokingFactoryBean();
        methodInvokingFactoryBean.setTargetClass(SecurityContextHolder.class);
        methodInvokingFactoryBean.setTargetMethod("setStrategyName");
        methodInvokingFactoryBean.setArguments(new String[]{SecurityContextHolder.MODE_INHERITABLETHREADLOCAL});
        return methodInvokingFactoryBean;
    }
}

最后,我发现了这篇文章Spring Security and @Async。我尝试使用CustomThreadPoolTask​​Executor覆盖execute方法。但这种方法永远不会运行。运行的ThreadPoolTask​​Executor的方法是:

<T> Future<T> submit(Callable<T> task)

我的代码:

@Configuration
@EnableAsync
public class SpringAsyncConfig implements AsyncConfigurer {

    @Override
    @Bean(name = "asyncExecutor")
    public Executor getAsyncExecutor() {

        CustomThreadPoolTaskExecutor executor = new CustomThreadPoolTaskExecutor();

        executor.setMaxPoolSize(1);
        executor.setThreadGroupName("MyCustomExecutor");
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setBeanName("asyncExecutor");
        executor.initialize();

        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new CustomAsyncExceptionHandler();
    }

}

public class CustomThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {

    private static final long serialVersionUID = 1L;

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

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

我的自定义执行方法永远不会运行。 我在Custom ThreadPoolTask​​Executor中做错了什么?获取运行异步方法的用户的其他一些方法。没有上下文的当前用户。

4 个答案:

答案 0 :(得分:4)

Spring支持通过传播的SecurityContext发送异步请求。从编程模型的角度来看,新功能看似简单。您可以通过在安全配置程序类中设置安全内容策略名称来以异步方法访问用户信息:

@Configuration
@EnableAsync
public class SpringAsyncConfig implements AsyncConfigurer {

    public SpringAsyncConfig() {
        SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
    }

}

答案 1 :(得分:1)

也许这有助于调用execute-method(在我的情况下工作):

@Override
@Bean(name = "asyncExecutor")
public Executor getAsyncExecutor() {

    CustomThreadPoolTaskExecutor executor = new CustomThreadPoolTaskExecutor();

    executor.setMaxPoolSize(1);
    executor.setThreadGroupName("MyCustomExecutor");
    executor.setWaitForTasksToCompleteOnShutdown(true);
    executor.setBeanName("asyncExecutor");
    executor.initialize();

    return new DelegatingSecurityContextAsyncTaskExecutor(executor);
}

答案 2 :(得分:0)

这是我的实现,我使用TaskDecorator在新线程中复制SecurityContext,新线程甚至在用户执行异步任务期间注销时都使用此新SecurityContext运行。

执行者的Xml声明

SELECT
  NULLIF(field_1, '') AS field_1,
  NULLIF(field_2, '') AS field_2
  ....

这是类SecurityContextCopyingDecorator

<bean id="securityContextCopyingDecorator" class="com.myapp.task.SecurityContextCopyingDecorator"/>

<bean id="threadPoolTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
    <property name="corePoolSize" value="1"/>
    <property name="maxPoolSize" value="5"/>
    <property name="queueCapacity" value="100"/> 
    <property name="taskDecorator" ref="securityContextCopyingDecorator"/>   
</bean>

<task:annotation-driven executor="threadPoolTaskExecutor"/>

最后注释一些在异步模式下使用threadPoolTask​​Executor的方法

package com.myapp.task;

import org.springframework.core.task.TaskDecorator;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;

public class SecurityContextCopyingDecorator implements TaskDecorator {

    @Override
      public Runnable decorate(Runnable runnable) {
        final Authentication a = SecurityContextHolder.getContext().getAuthentication();
        return () -> {
          try {
              SecurityContext ctx = SecurityContextHolder.createEmptyContext();
              ctx.setAuthentication(a);
              SecurityContextHolder.setContext(ctx);
            runnable.run();
          } finally {
              SecurityContextHolder.clearContext();
          }
        };
      }


}

答案 3 :(得分:0)

MODE_INHERITABLETHREADLOCAL 如果您有池化线程(阅读更多信息 here),则不应使用

@EnableAsync,如果您拥有带有 @Async 的现代 Spring Boot 应用程序,就会出现这种情况。

要在 DelegatingSecurityContextAsyncTaskExecutor 方法中保留安全上下文并使用默认的 Spring Boot 线程池,只需在 AsyncConfigurer 中定义 @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); } }

html