【问题标题】:Spring PreAuthorize SecurityContext not populated on parallelStream未在 parallelStream 上填充 Spring PreAuthorize SecurityContext
【发布时间】:2020-04-18 00:49:58
【问题描述】:

我有 2 个方法,每个方法都在一个单独的 Spring Bean 中:

汽车服务:

PreAuthorize("someCheck(#carId)")
public List<Color> getCarColors(String carId) {
  return this.getCar(carId).getColors().parallelStream()
    .map(ColorService::getColor)
    .collect(Collectors.toList());
}

色彩服务:

PreAuthorize ("someOtherCeck(#colorId)")
public Color getColor(String colorId) {
  return this.colorRepository.findById(colorId);
}

这些方法只是为了举例,只是简单地解释问题的最简单方法。

第一次检查 (someCheck) 顺利通过,但第二次检查 (someOtherCheck) 抛出异常:

在 SecurityContext 中找不到 Authentication 对象

我知道parallelStream 使用多个线程,所以我在application.properties 中添加了以下行:

spring.security.strategy=MODE_INHERITABLETHREADLOCAL

但这并不能解决问题,SecurityContext 没有填充到新线程中,并且第二个 PreAuthorize 抱怨在 SecurityContext 中找不到任何身份验证。

【问题讨论】:

    标签: java spring spring-boot spring-security java-stream


    【解决方案1】:

    当您使用parallelStream 时,您使用的是Java 本地线程池中的线程。您需要使用 Spring 的@Async 才能激活MODE_INHERITABLETHREADLOCAL

    要使用@Async,需要先设置一个线程池。

    @Configuration
    @EnableAsync
    public class AsyncExecutionConfiguration extends AsyncConfigurerSupport {
       @PostConstruct
       protected void init() { 
           SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
       }
    
        @Override
        @Primary
        @Bean
        public Executor getAsyncExecutor() {
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            // Copy the current RequestContext to each and every new @Async task
            executor.setCorePoolSize(standardPoolConfigs.getCorePoolSize());
            executor.setMaxPoolSize(standardPoolConfigs.getMaxPoolSize());
            executor.setQueueCapacity(standardPoolConfigs.getQueueCapacity());
            executor.setThreadPriority(standardPoolConfigs.getThreadPriority());
            executor.setThreadNamePrefix("async-thread-");
            executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
            executor.initialize();
    
            return executor;
        }
    }
    

    然后你可以使用CompletableFuture来捕获结果。

    @Async
    public CompletableFuture<Color> getColor(String colorId) {
        return CompletableFuture.completedFuture(this.colorRepository.findById(colorId));
    }
    

    那你就可以这样串流了。

    @Service
    public class ColorService {
        @Resource
        private ColorService self;
    
        public Collection<Color> getCarColors(String carId) {
            Queue<Color> colors = new ConcurrentLinkedQueue<>();
    
            this.getCar(carId)
                .getColors()
                .stream()
                .map(colorId -> self.getColor(colorId)
                                    .thenAccept(colors::add))
                .collect(CompletableFutures.joinList());
    
            return colors;
        }
    }
    

    将此添加到您的pom.xml

    <dependency>
      <groupId>com.spotify</groupId>
      <artifactId>completable-futures</artifactId>
      <version>${completable-futures.version}</version>
    </dependency>
    

    即使没有@Async,您实际上仍然可以使用public Color getColor(String colorId),而不是使用public CompletableFuture&lt;Color&gt; getColor(String colorId)。但是,您需要将调用者更改为使用CompletableFuture.supplyAsync。然后你可以将你在@Configuration中创建的线程池传递给supplyAsync。我会让你探索那条路;)

    【讨论】:

    • 正如我所说,代码只是说明问题的示例。您不必担心此代码会被真正使用。您能否指出一些描述如何组合 parallelStream、@Async 和 MODE_INHERITABLETHREADLOCAL 的文档?
    • 抱歉,我没看到那部分 :))。我浏览了你的帖子。用一些代码更新了我的帖子。
    • 谢谢!但是,没有开箱即用的支持来实际使用带有 PreAuthorize 注释的 parallelStream?
    • @Titulum:我不知道。大多数时候,Spring 的东西只适用于 Spring 的东西 :)
    猜你喜欢
    • 2017-04-06
    • 1970-01-01
    • 2016-04-21
    • 1970-01-01
    • 2015-01-30
    • 1970-01-01
    • 2017-02-16
    • 2014-11-04
    • 1970-01-01
    相关资源
    最近更新 更多