Java Future — Spring Authentication не имеет значения для AuditorAware

Это мой сценарий:

В моем приложении включен аудит Mongo с пользовательским AuditorAware, который получает текущего пользователя из файла SecurityContext. Это хорошо работает с синхронными методами, и текущий аудитор успешно сохраняется, но я не могу заставить его работать должным образом с методами @Async.

У меня есть асинхронный метод (CompletableFuture), который вносит некоторые обновления в мою базу данных Mongo. Когда вызывается AuditorAware.getCurrentAuditor(), информации об аутентификации не существует, и я не могу получить текущего аудитора (SecurityContextHolder.getContext().getAuthentication() возвращает null).

@Override
public User getCurrentAuditor() {
   Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

   if (authentication == null || !authentication.isAuthenticated()
                || authentication instanceof AnonymousAuthenticationToken) {
            log.error("Not authenticated");
            return null;
    }

    [...]

}

Я использую DelegatingSecurityContextAsyncTaskExecutor:

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(20);
        executor.setMaxPoolSize(100);
        executor.setQueueCapacity(200);
        executor.initialize();

        return new DelegatingSecurityContextAsyncTaskExecutor(executor);
    }

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

} 

Как я могу заставить его работать правильно?


person s1moner3d    schedule 31.10.2016    source источник


Ответы (2)


Контекст безопасности Spring всегда привязан к Threadlocal.

Возможно, вы можете дополнительно установить MODE_INHERITABLETHREADLOCAL для контекста безопасности.

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

http://www.ogrigas.eu/spring/2010/04/inherit-spring-security-context-in-child-threads

Как настроить стратегию Spring Security SecurityContextHolder?

person kuhajeyan    schedule 31.10.2016
comment
Согласно этому вопросу, использование MODE_INHERITABLETHREADLOCAL не будет работать при использовании Executor, который повторно использует потоки (наследование происходит только при создании нить). Однако он должен работать с DelegatingSecurityContextAsyncTaskExecutor. @ s1moner3d Вы уверены, что исполнитель, который вы указали в вопросе, действительно используется? CompletableFuture по умолчанию использует общий ForkJoinPool. - person Didier L; 02.11.2016
comment
@DidierL Как я могу это проверить? Я уверен, что он создается во время загрузки - person s1moner3d; 02.11.2016
comment
@DidierL Я проверил, и ты абсолютно прав! Он использует ForkJoinPool. Как я могу заставить его использовать DelegatingSecurityContextAsyncTaskExecutor? - person s1moner3d; 02.11.2016
comment
@ s1moner3d Думаю, проще всего отладить один из методов @Async, чтобы увидеть, в каком потоке он выполняется, или напечатать Thread.getCurrentThread().getName() в этом методе. - person Didier L; 02.11.2016
comment
@ s1moner3d это зависит от того, как вы создаете свой CompletableFuture. Большинство методов имеют перегрузки с аргументом Executor, но если вы полагаетесь на Spring @Async, я думаю, у вас есть фабрика, которая обрабатывает их создание, поэтому это должно быть сделано там. - person Didier L; 02.11.2016
comment
Я новичок в Futures. У меня опубликовано AsyncConfigurer, и я получаю задания CompletableFuture.supplyAsync(). Как я могу автоматически связать свой DelegatingSecurityContextAsyncTaskExecutor и передать его supplyAsync? - person s1moner3d; 02.11.2016

После комментариев к ответу kuhajeyan кажется, что вы неправильно используете CompletableFuture с Spring @Async.

Если вы запускаете свои задачи, используя, например. CompletableFuture.supplyAsync(Supplier), они будут выполняться обычный ForkJoinPool, а не тот, который вы настроили для @Async. Вы можете использовать перегрузки, которые принимают Executor в качестве аргумента, но на самом деле это не принесет пользы от преимуществ @Async.

Вместо этого вы должны позволить Spring обработать выполнение задачи и просто вернуть completed CompletableFuture следующим образом:

@Async
public CompletableFuture<String> someMethod() {
    // do some computation, but return a completed future
    return CompletableFuture.completedFuture("Hello World!");
}

Затем Spring выполнит ваш метод асинхронно в настроенном исполнителе, немедленно вернув CompletableFuture, который будет завершен, когда ваш метод вернется.

Если вы используете Spring 4.2 или более позднюю версию, это поддерживается по умолчанию. В противном случае требуется небольшая реализация, но это уже другой вопрос.

person Didier L    schedule 02.11.2016